From e644b27201e33dd2c8343b051a151162c88ab29e Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Sat, 23 Mar 2024 16:26:22 +0100 Subject: [PATCH] feat(pdfengines): add write metadata option on merge route --- pkg/modules/pdfengines/routes.go | 18 ++++ pkg/modules/pdfengines/routes_test.go | 127 ++++++++++++++++---------- 2 files changed, 97 insertions(+), 48 deletions(-) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index 0b6d8449..100dfe78 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -27,12 +27,22 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { inputPaths []string pdfa string pdfua bool + metadata map[string]interface{} ) err := ctx.FormData(). MandatoryPaths([]string{".pdf"}, &inputPaths). String("pdfa", &pdfa, ""). Bool("pdfua", &pdfua, false). + Custom("metadata", func(value string) error { + if len(value) > 0 { + err := json.Unmarshal([]byte(value), &metadata) + if err != nil { + return fmt.Errorf("unmarshal metadata: %w", err) + } + } + return nil + }). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) @@ -68,6 +78,14 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { outputPath = convertOutputPath } + // Writes and potentially overrides metadata entries, if any. + if len(metadata) > 0 { + err = engine.WriteMetadata(ctx, ctx.Log(), metadata, outputPath) + if err != nil { + return fmt.Errorf("write metadata: %w", err) + } + } + // Last but not least, add the output path to the context so that // the API is able to send it as a response to the client. err = ctx.AddOutputPaths(outputPath) diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index 52799811..fc16cde1 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -35,7 +35,27 @@ func TestMergeHandler(t *testing.T) { expectOutputPathsCount: 0, }, { - scenario: "error from PDF engine", + scenario: "invalid metadata form field", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + "file2.pdf": "/file2.pdf", + }) + ctx.SetValues(map[string][]string{ + "metadata": { + "foo", + }, + }) + return ctx + }(), + expectError: true, + expectHttpError: true, + expectHttpStatus: http.StatusBadRequest, + expectOutputPathsCount: 0, + }, + { + scenario: "PDF engine merge error", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -53,6 +73,60 @@ func TestMergeHandler(t *testing.T) { expectHttpError: false, expectOutputPathsCount: 0, }, + { + scenario: "PDF engine convert error", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + "file2.pdf": "/file2.pdf", + }) + ctx.SetValues(map[string][]string{ + "pdfa": { + gotenberg.PdfA1b, + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, + { + scenario: "PDF engine write metadata error", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetFiles(map[string]string{ + "file.pdf": "/file.pdf", + "file2.pdf": "/file2.pdf", + }) + ctx.SetValues(map[string][]string{ + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, + }) + return ctx + }(), + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return errors.New("foo") + }, + }, + expectError: true, + expectHttpError: false, + expectOutputPathsCount: 0, + }, { scenario: "cannot add output paths", ctx: func() *api.ContextMock { @@ -75,25 +149,6 @@ func TestMergeHandler(t *testing.T) { }, { scenario: "success", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { - return nil - }, - }, - expectError: false, - expectHttpError: false, - expectOutputPathsCount: 1, - }, - { - scenario: "error from PDF engine (convert)", ctx: func() *api.ContextMock { ctx := &api.ContextMock{Context: new(api.Context)} ctx.SetFiles(map[string]string{ @@ -104,6 +159,9 @@ func TestMergeHandler(t *testing.T) { "pdfa": { gotenberg.PdfA1b, }, + "metadata": { + "{\"Creator\": \"foo\", \"Producer\": \"bar\" }", + }, }) return ctx }(), @@ -112,36 +170,9 @@ func TestMergeHandler(t *testing.T) { return nil }, ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { - return errors.New("foo") - }, - }, - expectError: true, - expectHttpError: false, - expectOutputPathsCount: 0, - }, - { - scenario: "success with PDF/A & PDF/UA form fields", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetFiles(map[string]string{ - "file.pdf": "/file.pdf", - "file2.pdf": "/file2.pdf", - }) - ctx.SetValues(map[string][]string{ - "pdfa": { - gotenberg.PdfA1b, - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - engine: &gotenberg.PdfEngineMock{ - MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { return nil }, - ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { return nil }, },