feat(pdfengines): one flag per PDF engine method for a more granular selection of PDF engines

This commit is contained in:
Julien Neuhart
2024-10-13 11:29:38 +02:00
parent 8ff9d3bcf1
commit 249745f256
5 changed files with 370 additions and 158 deletions

View File

@@ -72,6 +72,10 @@ LOG_LEVEL=info
LOG_FORMAT=auto LOG_FORMAT=auto
LOG_FIELDS_PREFIX= LOG_FIELDS_PREFIX=
PDFENGINES_ENGINES= PDFENGINES_ENGINES=
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
PDFENGINES_READ_METADATA_ENGINES=exiftool
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
PDFENGINES_DISABLE_ROUTES=false PDFENGINES_DISABLE_ROUTES=false
PROMETHEUS_NAMESPACE=gotenberg PROMETHEUS_NAMESPACE=gotenberg
PROMETHEUS_COLLECT_INTERVAL=1s PROMETHEUS_COLLECT_INTERVAL=1s
@@ -136,6 +140,10 @@ run: ## Start a Gotenberg container
--log-format=$(LOG_FORMAT) \ --log-format=$(LOG_FORMAT) \
--log-fields-prefix=$(LOG_FIELDS_PREFIX) \ --log-fields-prefix=$(LOG_FIELDS_PREFIX) \
--pdfengines-engines=$(PDFENGINES_ENGINES) \ --pdfengines-engines=$(PDFENGINES_ENGINES) \
--pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \
--pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \
--pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \
--pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \
--pdfengines-disable-routes=$(PDFENGINES_DISABLE_ROUTES) \ --pdfengines-disable-routes=$(PDFENGINES_DISABLE_ROUTES) \
--prometheus-namespace=$(PROMETHEUS_NAMESPACE) \ --prometheus-namespace=$(PROMETHEUS_NAMESPACE) \
--prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \ --prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \

View File

@@ -12,12 +12,23 @@ import (
) )
type multiPdfEngines struct { type multiPdfEngines struct {
engines []gotenberg.PdfEngine mergeEngines []gotenberg.PdfEngine
convertEngines []gotenberg.PdfEngine
readMedataEngines []gotenberg.PdfEngine
writeMedataEngines []gotenberg.PdfEngine
} }
func newMultiPdfEngines(engines ...gotenberg.PdfEngine) *multiPdfEngines { func newMultiPdfEngines(
mergeEngines,
convertEngines,
readMetadataEngines,
writeMedataEngines []gotenberg.PdfEngine,
) *multiPdfEngines {
return &multiPdfEngines{ return &multiPdfEngines{
engines: engines, mergeEngines: mergeEngines,
convertEngines: convertEngines,
readMedataEngines: readMetadataEngines,
writeMedataEngines: writeMedataEngines,
} }
} }
@@ -27,7 +38,7 @@ func (multi *multiPdfEngines) Merge(ctx context.Context, logger *zap.Logger, inp
var err error var err error
errChan := make(chan error, 1) errChan := make(chan error, 1)
for _, engine := range multi.engines { for _, engine := range multi.mergeEngines {
go func(engine gotenberg.PdfEngine) { go func(engine gotenberg.PdfEngine) {
errChan <- engine.Merge(ctx, logger, inputPaths, outputPath) errChan <- engine.Merge(ctx, logger, inputPaths, outputPath)
}(engine) }(engine)
@@ -52,7 +63,7 @@ func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, f
var err error var err error
errChan := make(chan error, 1) errChan := make(chan error, 1)
for _, engine := range multi.engines { for _, engine := range multi.convertEngines {
go func(engine gotenberg.PdfEngine) { go func(engine gotenberg.PdfEngine) {
errChan <- engine.Convert(ctx, logger, formats, inputPath, outputPath) errChan <- engine.Convert(ctx, logger, formats, inputPath, outputPath)
}(engine) }(engine)
@@ -80,16 +91,16 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logg
var err error var err error
var mu sync.Mutex // to safely append errors. var mu sync.Mutex // to safely append errors.
resultChan := make(chan readMetadataResult, len(multi.engines)) resultChan := make(chan readMetadataResult, len(multi.readMedataEngines))
for _, engine := range multi.engines { for _, engine := range multi.readMedataEngines {
go func(engine gotenberg.PdfEngine) { go func(engine gotenberg.PdfEngine) {
metadata, err := engine.ReadMetadata(ctx, logger, inputPath) metadata, err := engine.ReadMetadata(ctx, logger, inputPath)
resultChan <- readMetadataResult{metadata: metadata, err: err} resultChan <- readMetadataResult{metadata: metadata, err: err}
}(engine) }(engine)
} }
for range multi.engines { for range multi.readMedataEngines {
select { select {
case result := <-resultChan: case result := <-resultChan:
if result.err != nil { if result.err != nil {
@@ -111,7 +122,7 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Log
var err error var err error
errChan := make(chan error, 1) errChan := make(chan error, 1)
for _, engine := range multi.engines { for _, engine := range multi.writeMedataEngines {
go func(engine gotenberg.PdfEngine) { go func(engine gotenberg.PdfEngine) {
errChan <- engine.WriteMetadata(ctx, logger, metadata, inputPath) errChan <- engine.WriteMetadata(ctx, logger, metadata, inputPath)
}(engine) }(engine)

View File

@@ -20,11 +20,16 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
{ {
scenario: "nominal behavior", scenario: "nominal behavior",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ []gotenberg.PdfEngine{
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return nil MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
expectError: false, expectError: false,
@@ -32,16 +37,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
{ {
scenario: "at least one engine does not return an error", scenario: "at least one engine does not return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ []gotenberg.PdfEngine{
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return errors.New("foo") MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
}, return errors.New("foo")
}, },
&gotenberg.PdfEngineMock{ },
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return nil MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
expectError: false, expectError: false,
@@ -49,16 +59,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
{ {
scenario: "all engines return an error", scenario: "all engines return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ []gotenberg.PdfEngine{
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return errors.New("foo") MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
}, return errors.New("foo")
}, },
&gotenberg.PdfEngineMock{ },
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return errors.New("foo") MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
return errors.New("foo")
},
}, },
}, },
nil,
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
expectError: true, expectError: true,
@@ -66,11 +81,16 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
{ {
scenario: "context expired", scenario: "context expired",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ []gotenberg.PdfEngine{
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { &gotenberg.PdfEngineMock{
return nil MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
nil,
), ),
ctx: func() context.Context { ctx: func() context.Context {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -105,43 +125,58 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
{ {
scenario: "nominal behavior", scenario: "nominal behavior",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { []gotenberg.PdfEngine{
return nil &gotenberg.PdfEngineMock{
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
{ {
scenario: "at least one engine does not return an error", scenario: "at least one engine does not return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { []gotenberg.PdfEngine{
return errors.New("foo") &gotenberg.PdfEngineMock{
}, ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
}, return errors.New("foo")
&gotenberg.PdfEngineMock{ },
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { },
return nil &gotenberg.PdfEngineMock{
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
{ {
scenario: "all engines return an error", scenario: "all engines return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { []gotenberg.PdfEngine{
return errors.New("foo") &gotenberg.PdfEngineMock{
}, ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
}, return errors.New("foo")
&gotenberg.PdfEngineMock{ },
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { },
return errors.New("foo") &gotenberg.PdfEngineMock{
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
return errors.New("foo")
},
}, },
}, },
nil,
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
expectError: true, expectError: true,
@@ -149,11 +184,16 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
{ {
scenario: "context expired", scenario: "context expired",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { []gotenberg.PdfEngine{
return nil &gotenberg.PdfEngineMock{
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
return nil
},
}, },
}, },
nil,
nil,
), ),
ctx: func() context.Context { ctx: func() context.Context {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -188,43 +228,58 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
{ {
scenario: "nominal behavior", scenario: "nominal behavior",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { nil,
return make(map[string]interface{}), nil []gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
return make(map[string]interface{}), nil
},
}, },
}, },
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
{ {
scenario: "at least one engine does not return an error", scenario: "at least one engine does not return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { nil,
return nil, errors.New("foo") []gotenberg.PdfEngine{
}, &gotenberg.PdfEngineMock{
}, ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
&gotenberg.PdfEngineMock{ return nil, errors.New("foo")
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { },
return make(map[string]interface{}), nil },
&gotenberg.PdfEngineMock{
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
return make(map[string]interface{}), nil
},
}, },
}, },
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
{ {
scenario: "all engines return an error", scenario: "all engines return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { nil,
return nil, errors.New("foo") []gotenberg.PdfEngine{
}, &gotenberg.PdfEngineMock{
}, ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
&gotenberg.PdfEngineMock{ return nil, errors.New("foo")
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { },
return nil, errors.New("foo") },
&gotenberg.PdfEngineMock{
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
return nil, errors.New("foo")
},
}, },
}, },
nil,
), ),
ctx: context.Background(), ctx: context.Background(),
expectError: true, expectError: true,
@@ -232,11 +287,16 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
{ {
scenario: "context expired", scenario: "context expired",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { nil,
return make(map[string]interface{}), nil []gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
return make(map[string]interface{}), nil
},
}, },
}, },
nil,
), ),
ctx: func() context.Context { ctx: func() context.Context {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -271,9 +331,14 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
{ {
scenario: "nominal behavior", scenario: "nominal behavior",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { nil,
return nil nil,
[]gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
return nil
},
}, },
}, },
), ),
@@ -282,14 +347,19 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
{ {
scenario: "at least one engine does not return an error", scenario: "at least one engine does not return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { nil,
return errors.New("foo") nil,
[]gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
return errors.New("foo")
},
}, },
}, &gotenberg.PdfEngineMock{
&gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil
return nil },
}, },
}, },
), ),
@@ -298,14 +368,19 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
{ {
scenario: "all engines return an error", scenario: "all engines return an error",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { nil,
return errors.New("foo") nil,
[]gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
return errors.New("foo")
},
}, },
}, &gotenberg.PdfEngineMock{
&gotenberg.PdfEngineMock{ WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return errors.New("foo")
return errors.New("foo") },
}, },
}, },
), ),
@@ -315,9 +390,14 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
{ {
scenario: "context expired", scenario: "context expired",
engine: newMultiPdfEngines( engine: newMultiPdfEngines(
&gotenberg.PdfEngineMock{ nil,
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { nil,
return nil nil,
[]gotenberg.PdfEngine{
&gotenberg.PdfEngineMock{
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
return nil
},
}, },
}, },
), ),

View File

@@ -27,9 +27,12 @@ func init() {
// the [api.Router] interface to expose relevant PDF processing routes if // the [api.Router] interface to expose relevant PDF processing routes if
// enabled. // enabled.
type PdfEngines struct { type PdfEngines struct {
names []string mergeNames []string
engines []gotenberg.PdfEngine convertNames []string
disableRoutes bool readMetadataNames []string
writeMedataNames []string
engines []gotenberg.PdfEngine
disableRoutes bool
} }
// Descriptor returns a PdfEngines' module descriptor. // Descriptor returns a PdfEngines' module descriptor.
@@ -38,9 +41,18 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
ID: "pdfengines", ID: "pdfengines",
FlagSet: func() *flag.FlagSet { FlagSet: func() *flag.FlagSet {
fs := flag.NewFlagSet("pdfengines", flag.ExitOnError) fs := flag.NewFlagSet("pdfengines", flag.ExitOnError)
fs.StringSlice("pdfengines-engines", make([]string, 0), "Set the PDF engines and their order - all by default") fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature")
fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature")
fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read metadata feature")
fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature")
fs.Bool("pdfengines-disable-routes", false, "Disable the routes") fs.Bool("pdfengines-disable-routes", false, "Disable the routes")
fs.StringSlice("pdfengines-engines", make([]string, 0), "Set the default PDF engines and their default order - all by default")
err := fs.MarkDeprecated("pdfengines-engines", "use other flags for a more granular selection of PDF engines per method")
if err != nil {
panic(err)
}
return fs return fs
}(), }(),
New: func() gotenberg.Module { return new(PdfEngines) }, New: func() gotenberg.Module { return new(PdfEngines) },
@@ -51,7 +63,10 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
// selected by the user thanks to the "engines" flag. // selected by the user thanks to the "engines" flag.
func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
flags := ctx.ParsedFlags() flags := ctx.ParsedFlags()
names := flags.MustStringSlice("pdfengines-engines") mergeNames := flags.MustStringSlice("pdfengines-merge-engines")
convertNames := flags.MustStringSlice("pdfengines-convert-engines")
readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines")
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes") mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
engines, err := ctx.Modules(new(gotenberg.PdfEngine)) engines, err := ctx.Modules(new(gotenberg.PdfEngine))
@@ -65,26 +80,37 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
mod.engines[i] = engine.(gotenberg.PdfEngine) mod.engines[i] = engine.(gotenberg.PdfEngine)
} }
if len(names) > 0 { defaultNames := make([]string, len(mod.engines))
// Selection from user. for i, engine := range mod.engines {
mod.names = names defaultNames[i] = engine.(gotenberg.Module).Descriptor().ID
// Example in case of deprecated module name.
//for i, name := range names {
// if name == "unoconv-pdfengine" || name == "uno-pdfengine" {
// logger.Warn(fmt.Sprintf("%s is deprecated; prefer libreoffice-pdfengine instead", name))
// mod.names[i] = "libreoffice-pdfengine"
// }
//}
return nil
} }
// No selection from user, use all PDF engines available. // Example in case of deprecated module name.
mod.names = make([]string, len(mod.engines)) //for i, name := range defaultNames {
// if name == "unoconv-pdfengine" || name == "uno-pdfengine" {
// logger.Warn(fmt.Sprintf("%s is deprecated; prefer libreoffice-pdfengine instead", name))
// mod.defaultNames[i] = "libreoffice-pdfengine"
// }
//}
for i, engine := range mod.engines { mod.mergeNames = defaultNames
mod.names[i] = engine.(gotenberg.Module).Descriptor().ID if len(mergeNames) > 0 {
mod.mergeNames = mergeNames
}
mod.convertNames = defaultNames
if len(convertNames) > 0 {
mod.convertNames = convertNames
}
mod.readMetadataNames = defaultNames
if len(readMetadataNames) > 0 {
mod.readMetadataNames = readMetadataNames
}
mod.writeMedataNames = defaultNames
if len(writeMetadataNames) > 0 {
mod.writeMedataNames = writeMetadataNames
} }
return nil return nil
@@ -105,22 +131,40 @@ func (mod *PdfEngines) Validate() error {
} }
nonExistingEngines := make([]string, 0) nonExistingEngines := make([]string, 0)
findNonExistingEngines := func(names []string) {
for _, name := range names {
engineExists := false
for _, name := range mod.names { for _, engine := range mod.engines {
engineExists := false if name == engine.(gotenberg.Module).Descriptor().ID {
engineExists = true
break
}
}
for _, engine := range mod.engines { if engineExists {
if name == engine.(gotenberg.Module).Descriptor().ID { continue
engineExists = true }
break
alreadyInSlice := false
for _, engine := range nonExistingEngines {
if engine == name {
alreadyInSlice = true
break
}
}
if !alreadyInSlice {
nonExistingEngines = append(nonExistingEngines, name)
} }
} }
if !engineExists {
nonExistingEngines = append(nonExistingEngines, name)
}
} }
findNonExistingEngines(mod.mergeNames)
findNonExistingEngines(mod.convertNames)
findNonExistingEngines(mod.readMetadataNames)
findNonExistingEngines(mod.writeMedataNames)
if len(nonExistingEngines) == 0 { if len(nonExistingEngines) == 0 {
return nil return nil
} }
@@ -132,24 +176,35 @@ func (mod *PdfEngines) Validate() error {
// modules. // modules.
func (mod *PdfEngines) SystemMessages() []string { func (mod *PdfEngines) SystemMessages() []string {
return []string{ return []string{
strings.Join(mod.names[:], " "), fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
} }
} }
// PdfEngine returns a [gotenberg.PdfEngine]. // PdfEngine returns a [gotenberg.PdfEngine].
func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) {
engines := make([]gotenberg.PdfEngine, len(mod.names)) engines := func(names []string) []gotenberg.PdfEngine {
list := make([]gotenberg.PdfEngine, len(names))
for i, name := range mod.names { for i, name := range names {
for _, engine := range mod.engines { for _, engine := range mod.engines {
if name == engine.(gotenberg.Module).Descriptor().ID { if name == engine.(gotenberg.Module).Descriptor().ID {
engines[i] = engine list[i] = engine
break break
}
} }
} }
return list
} }
return newMultiPdfEngines(engines...), nil return newMultiPdfEngines(
engines(mod.mergeNames),
engines(mod.convertNames),
engines(mod.readMetadataNames),
engines(mod.writeMedataNames),
), nil
} }
// Routes returns the HTTP routes. // Routes returns the HTTP routes.

View File

@@ -2,6 +2,7 @@ package pdfengines
import ( import (
"errors" "errors"
"fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@@ -22,10 +23,13 @@ func TestPdfEngines_Descriptor(t *testing.T) {
func TestPdfEngines_Provision(t *testing.T) { func TestPdfEngines_Provision(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
scenario string scenario string
ctx *gotenberg.Context ctx *gotenberg.Context
expectedPdfEngines []string expectedMergePdfEngines []string
expectError bool expectedConvertPdfEngines []string
expectedReadMetadataPdfEngines []string
expectedWriteMetadataPdfEngines []string
expectError bool
}{ }{
{ {
scenario: "no selection from user", scenario: "no selection from user",
@@ -61,8 +65,11 @@ func TestPdfEngines_Provision(t *testing.T) {
}, },
) )
}(), }(),
expectedPdfEngines: []string{"bar"}, expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"},
expectError: false, expectedConvertPdfEngines: []string{"libreoffice-pdfengine"},
expectedReadMetadataPdfEngines: []string{"exiftool"},
expectedWriteMetadataPdfEngines: []string{"exiftool"},
expectError: false,
}, },
{ {
scenario: "selection from user", scenario: "selection from user",
@@ -100,7 +107,7 @@ func TestPdfEngines_Provision(t *testing.T) {
} }
fs := new(PdfEngines).Descriptor().FlagSet fs := new(PdfEngines).Descriptor().FlagSet
err := fs.Parse([]string{"--pdfengines-engines=b", "--pdfengines-engines=a"}) err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
if err != nil { if err != nil {
t.Fatalf("expected no error but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
@@ -116,8 +123,12 @@ func TestPdfEngines_Provision(t *testing.T) {
}, },
) )
}(), }(),
expectedPdfEngines: []string{"b", "a"},
expectError: false, expectedMergePdfEngines: []string{"b"},
expectedConvertPdfEngines: []string{"b"},
expectedReadMetadataPdfEngines: []string{"a"},
expectedWriteMetadataPdfEngines: []string{"a"},
expectError: false,
}, },
{ {
scenario: "no valid PDF engine", scenario: "no valid PDF engine",
@@ -167,13 +178,43 @@ func TestPdfEngines_Provision(t *testing.T) {
t.Fatal("expected error but got none") t.Fatal("expected error but got none")
} }
if len(tc.expectedPdfEngines) != len(mod.names) { if len(tc.expectedMergePdfEngines) != len(mod.mergeNames) {
t.Fatalf("expected %d names but got %d", len(tc.expectedPdfEngines), len(mod.names)) t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames))
} }
for index, name := range mod.names { if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) {
if name != tc.expectedPdfEngines[index] { t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames))
t.Fatalf("expected scenario at index %d to be %s, but got: %s", index, name, tc.expectedPdfEngines[index]) }
if len(tc.expectedReadMetadataPdfEngines) != len(mod.readMetadataNames) {
t.Fatalf("expected %d read metadata names but got %d", len(tc.expectedReadMetadataPdfEngines), len(mod.readMetadataNames))
}
if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMedataNames) {
t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMedataNames))
}
for index, name := range mod.mergeNames {
if name != tc.expectedMergePdfEngines[index] {
t.Fatalf("expected merge name at index %d to be %s, but got: %s", index, name, tc.expectedMergePdfEngines[index])
}
}
for index, name := range mod.convertNames {
if name != tc.expectedConvertPdfEngines[index] {
t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index])
}
}
for index, name := range mod.readMetadataNames {
if name != tc.expectedReadMetadataPdfEngines[index] {
t.Fatalf("expected read metadata name at index %d to be %s, but got: %s", index, name, tc.expectedReadMetadataPdfEngines[index])
}
}
for index, name := range mod.writeMedataNames {
if name != tc.expectedWriteMetadataPdfEngines[index] {
t.Fatalf("expected write metadat name at index %d to be %s, but got: %s", index, name, tc.expectedWriteMetadataPdfEngines[index])
} }
} }
}) })
@@ -239,8 +280,11 @@ func TestPdfEngines_Validate(t *testing.T) {
} { } {
t.Run(tc.scenario, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
mod := PdfEngines{ mod := PdfEngines{
names: tc.names, mergeNames: tc.names,
engines: tc.engines, convertNames: tc.names,
readMetadataNames: tc.names,
writeMedataNames: tc.names,
engines: tc.engines,
} }
err := mod.Validate() err := mod.Validate()
@@ -258,22 +302,36 @@ func TestPdfEngines_Validate(t *testing.T) {
func TestPdfEngines_SystemMessages(t *testing.T) { func TestPdfEngines_SystemMessages(t *testing.T) {
mod := new(PdfEngines) mod := new(PdfEngines)
mod.names = []string{"foo", "bar"} mod.mergeNames = []string{"foo", "bar"}
mod.convertNames = []string{"foo", "bar"}
mod.readMetadataNames = []string{"foo", "bar"}
mod.writeMedataNames = []string{"foo", "bar"}
messages := mod.SystemMessages() messages := mod.SystemMessages()
if len(messages) != 1 { if len(messages) != 4 {
t.Errorf("expected one and only one message, but got %d", len(messages)) t.Errorf("expected one and only one message, but got %d", len(messages))
} }
expect := strings.Join(mod.names[:], " ") expect := []string{
if messages[0] != expect { fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
t.Errorf("expected message '%s', but got '%s'", expect, messages[0]) fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
}
for i, message := range messages {
if message != expect[i] {
t.Errorf("expected message at index %d to be %s, but got %s", i, message, expect[i])
}
} }
} }
func TestPdfEngines_PdfEngine(t *testing.T) { func TestPdfEngines_PdfEngine(t *testing.T) {
mod := PdfEngines{ mod := PdfEngines{
names: []string{"foo", "bar"}, mergeNames: []string{"foo", "bar"},
convertNames: []string{"foo", "bar"},
readMetadataNames: []string{"foo", "bar"},
writeMedataNames: []string{"foo", "bar"},
engines: func() []gotenberg.PdfEngine { engines: func() []gotenberg.PdfEngine {
engine1 := &struct { engine1 := &struct {
gotenberg.ModuleMock gotenberg.ModuleMock