diff --git a/Makefile b/Makefile index 67529de6..230519df 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,10 @@ LOG_LEVEL=info LOG_FORMAT=auto LOG_FIELDS_PREFIX= 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 PROMETHEUS_NAMESPACE=gotenberg PROMETHEUS_COLLECT_INTERVAL=1s @@ -136,6 +140,10 @@ run: ## Start a Gotenberg container --log-format=$(LOG_FORMAT) \ --log-fields-prefix=$(LOG_FIELDS_PREFIX) \ --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) \ --prometheus-namespace=$(PROMETHEUS_NAMESPACE) \ --prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \ diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index d948b413..4cbbc3ea 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -12,12 +12,23 @@ import ( ) 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{ - 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 errChan := make(chan error, 1) - for _, engine := range multi.engines { + for _, engine := range multi.mergeEngines { go func(engine gotenberg.PdfEngine) { errChan <- engine.Merge(ctx, logger, inputPaths, outputPath) }(engine) @@ -52,7 +63,7 @@ func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, f var err error errChan := make(chan error, 1) - for _, engine := range multi.engines { + for _, engine := range multi.convertEngines { go func(engine gotenberg.PdfEngine) { errChan <- engine.Convert(ctx, logger, formats, inputPath, outputPath) }(engine) @@ -80,16 +91,16 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logg var err error 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) { metadata, err := engine.ReadMetadata(ctx, logger, inputPath) resultChan <- readMetadataResult{metadata: metadata, err: err} }(engine) } - for range multi.engines { + for range multi.readMedataEngines { select { case result := <-resultChan: if result.err != nil { @@ -111,7 +122,7 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Log var err error errChan := make(chan error, 1) - for _, engine := range multi.engines { + for _, engine := range multi.writeMedataEngines { go func(engine gotenberg.PdfEngine) { errChan <- engine.WriteMetadata(ctx, logger, metadata, inputPath) }(engine) diff --git a/pkg/modules/pdfengines/multi_test.go b/pkg/modules/pdfengines/multi_test.go index 3a204b70..00e706d7 100644 --- a/pkg/modules/pdfengines/multi_test.go +++ b/pkg/modules/pdfengines/multi_test.go @@ -20,11 +20,16 @@ func TestMultiPdfEngines_Merge(t *testing.T) { { scenario: "nominal behavior", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, }, }, + nil, + nil, + nil, ), ctx: context.Background(), expectError: false, @@ -32,16 +37,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) { { scenario: "at least one engine does not return an error", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - 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 { - return nil + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + 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 { + return nil + }, }, }, + nil, + nil, + nil, ), ctx: context.Background(), expectError: false, @@ -49,16 +59,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) { { scenario: "all engines return an error", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - 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 { - return errors.New("foo") + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + 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 { + return errors.New("foo") + }, }, }, + nil, + nil, + nil, ), ctx: context.Background(), expectError: true, @@ -66,11 +81,16 @@ func TestMultiPdfEngines_Merge(t *testing.T) { { scenario: "context expired", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, }, }, + nil, + nil, + nil, ), ctx: func() context.Context { ctx, cancel := context.WithCancel(context.Background()) @@ -105,43 +125,58 @@ func TestMultiPdfEngines_Convert(t *testing.T) { { scenario: "nominal behavior", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil + nil, + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, }, }, + nil, + nil, ), ctx: context.Background(), }, { scenario: "at least one engine does not return an error", engine: newMultiPdfEngines( - &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 + nil, + []gotenberg.PdfEngine{ + &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 + }, }, }, + nil, + nil, ), ctx: context.Background(), }, { scenario: "all engines return an error", engine: newMultiPdfEngines( - &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, + []gotenberg.PdfEngine{ + &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(), expectError: true, @@ -149,11 +184,16 @@ func TestMultiPdfEngines_Convert(t *testing.T) { { scenario: "context expired", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return nil + nil, + []gotenberg.PdfEngine{ + &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, cancel := context.WithCancel(context.Background()) @@ -188,43 +228,58 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "nominal behavior", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { - return make(map[string]interface{}), nil + nil, + 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(), }, { scenario: "at least one engine does not return an error", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - 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 make(map[string]interface{}), nil + nil, + nil, + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + 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 make(map[string]interface{}), nil + }, }, }, + nil, ), ctx: context.Background(), }, { scenario: "all engines return an error", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - 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, + nil, + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + 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(), expectError: true, @@ -232,11 +287,16 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) { { scenario: "context expired", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) { - return make(map[string]interface{}), nil + nil, + 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, cancel := context.WithCancel(context.Background()) @@ -271,9 +331,14 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "nominal behavior", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil + nil, + 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", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") + nil, + nil, + 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{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil + &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, }, }, ), @@ -298,14 +368,19 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "all engines return an error", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") + nil, + nil, + 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{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return errors.New("foo") + &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return errors.New("foo") + }, }, }, ), @@ -315,9 +390,14 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) { { scenario: "context expired", engine: newMultiPdfEngines( - &gotenberg.PdfEngineMock{ - WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { - return nil + nil, + nil, + nil, + []gotenberg.PdfEngine{ + &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, }, }, ), diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index c71e9557..7303f34e 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -27,9 +27,12 @@ func init() { // the [api.Router] interface to expose relevant PDF processing routes if // enabled. type PdfEngines struct { - names []string - engines []gotenberg.PdfEngine - disableRoutes bool + mergeNames []string + convertNames []string + readMetadataNames []string + writeMedataNames []string + engines []gotenberg.PdfEngine + disableRoutes bool } // Descriptor returns a PdfEngines' module descriptor. @@ -38,9 +41,18 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { ID: "pdfengines", FlagSet: func() *flag.FlagSet { 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.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 }(), 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. func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { 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") 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) } - if len(names) > 0 { - // Selection from user. - mod.names = names - - // 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 + defaultNames := make([]string, len(mod.engines)) + for i, engine := range mod.engines { + defaultNames[i] = engine.(gotenberg.Module).Descriptor().ID } - // No selection from user, use all PDF engines available. - mod.names = make([]string, len(mod.engines)) + // Example in case of deprecated module name. + //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.names[i] = engine.(gotenberg.Module).Descriptor().ID + mod.mergeNames = defaultNames + 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 @@ -105,22 +131,40 @@ func (mod *PdfEngines) Validate() error { } nonExistingEngines := make([]string, 0) + findNonExistingEngines := func(names []string) { + for _, name := range names { + engineExists := false - for _, name := range mod.names { - engineExists := false + for _, engine := range mod.engines { + if name == engine.(gotenberg.Module).Descriptor().ID { + engineExists = true + break + } + } - for _, engine := range mod.engines { - if name == engine.(gotenberg.Module).Descriptor().ID { - engineExists = true - break + if engineExists { + continue + } + + 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 { return nil } @@ -132,24 +176,35 @@ func (mod *PdfEngines) Validate() error { // modules. func (mod *PdfEngines) SystemMessages() []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]. func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { - engines := make([]gotenberg.PdfEngine, len(mod.names)) - - for i, name := range mod.names { - for _, engine := range mod.engines { - if name == engine.(gotenberg.Module).Descriptor().ID { - engines[i] = engine - break + engines := func(names []string) []gotenberg.PdfEngine { + list := make([]gotenberg.PdfEngine, len(names)) + for i, name := range names { + for _, engine := range mod.engines { + if name == engine.(gotenberg.Module).Descriptor().ID { + list[i] = engine + 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. diff --git a/pkg/modules/pdfengines/pdfengines_test.go b/pkg/modules/pdfengines/pdfengines_test.go index bf12c8a5..fe999432 100644 --- a/pkg/modules/pdfengines/pdfengines_test.go +++ b/pkg/modules/pdfengines/pdfengines_test.go @@ -2,6 +2,7 @@ package pdfengines import ( "errors" + "fmt" "reflect" "strings" "testing" @@ -22,10 +23,13 @@ func TestPdfEngines_Descriptor(t *testing.T) { func TestPdfEngines_Provision(t *testing.T) { for _, tc := range []struct { - scenario string - ctx *gotenberg.Context - expectedPdfEngines []string - expectError bool + scenario string + ctx *gotenberg.Context + expectedMergePdfEngines []string + expectedConvertPdfEngines []string + expectedReadMetadataPdfEngines []string + expectedWriteMetadataPdfEngines []string + expectError bool }{ { scenario: "no selection from user", @@ -61,8 +65,11 @@ func TestPdfEngines_Provision(t *testing.T) { }, ) }(), - expectedPdfEngines: []string{"bar"}, - expectError: false, + expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"}, + expectedConvertPdfEngines: []string{"libreoffice-pdfengine"}, + expectedReadMetadataPdfEngines: []string{"exiftool"}, + expectedWriteMetadataPdfEngines: []string{"exiftool"}, + expectError: false, }, { scenario: "selection from user", @@ -100,7 +107,7 @@ func TestPdfEngines_Provision(t *testing.T) { } 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 { 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", @@ -167,13 +178,43 @@ func TestPdfEngines_Provision(t *testing.T) { t.Fatal("expected error but got none") } - if len(tc.expectedPdfEngines) != len(mod.names) { - t.Fatalf("expected %d names but got %d", len(tc.expectedPdfEngines), len(mod.names)) + if len(tc.expectedMergePdfEngines) != len(mod.mergeNames) { + t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames)) } - for index, name := range mod.names { - if name != tc.expectedPdfEngines[index] { - t.Fatalf("expected scenario at index %d to be %s, but got: %s", index, name, tc.expectedPdfEngines[index]) + if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) { + t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames)) + } + + 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) { mod := PdfEngines{ - names: tc.names, - engines: tc.engines, + mergeNames: tc.names, + convertNames: tc.names, + readMetadataNames: tc.names, + writeMedataNames: tc.names, + engines: tc.engines, } err := mod.Validate() @@ -258,22 +302,36 @@ func TestPdfEngines_Validate(t *testing.T) { func TestPdfEngines_SystemMessages(t *testing.T) { 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() - if len(messages) != 1 { + if len(messages) != 4 { t.Errorf("expected one and only one message, but got %d", len(messages)) } - expect := strings.Join(mod.names[:], " ") - if messages[0] != expect { - t.Errorf("expected message '%s', but got '%s'", expect, messages[0]) + expect := []string{ + 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[:], " ")), + } + + 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) { 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 { engine1 := &struct { gotenberg.ModuleMock