From ebf0548d19a6b73bbdeedd44c2650b8a401a1b6d Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 27 Mar 2026 22:14:01 +0100 Subject: [PATCH] refactor: switch from multierr to errors.join --- go.mod | 1 - go.sum | 2 - pkg/gotenberg/telemetry.go | 9 ++--- pkg/modules/api/api.go | 13 +++---- pkg/modules/api/formdata.go | 5 +-- pkg/modules/chromium/events.go | 14 +++---- pkg/modules/chromium/routes.go | 9 ++--- pkg/modules/libreoffice/api/api.go | 5 +-- pkg/modules/pdfengines/multi.go | 60 +++++++++++++++++------------- 9 files changed, 60 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index 4b3beebc..2e8504c9 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( go.opentelemetry.io/otel/sdk/log v0.18.0 go.opentelemetry.io/otel/sdk/metric v1.42.0 go.opentelemetry.io/otel/trace v1.42.0 - go.uber.org/multierr v1.11.0 golang.org/x/net v0.52.0 golang.org/x/sync v0.20.0 golang.org/x/term v0.41.0 diff --git a/go.sum b/go.sum index 3ec14d39..410f1c13 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,6 @@ go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpu go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw= diff --git a/pkg/gotenberg/telemetry.go b/pkg/gotenberg/telemetry.go index dd189a0a..b9b98430 100644 --- a/pkg/gotenberg/telemetry.go +++ b/pkg/gotenberg/telemetry.go @@ -11,7 +11,6 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" - "go.uber.org/multierr" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg/internal/log" internalotel "github.com/gotenberg/gotenberg/v8/pkg/gotenberg/internal/otel" @@ -55,13 +54,13 @@ func (cfg TelemetryConfig) Validate() error { var err error if cfg.ServiceName == "" { - err = multierr.Append(err, + err = errors.Join(err, errors.New("service name must not be empty"), ) } if cfg.ServiceVersion == "" { - err = multierr.Append(err, + err = errors.Join(err, errors.New("service version must not be empty"), ) } @@ -70,7 +69,7 @@ func (cfg TelemetryConfig) Validate() error { case ErrorLoggingLevel, WarnLoggingLevel, InfoLoggingLevel, DebugLoggingLevel: break default: - err = multierr.Append( + err = errors.Join( err, fmt.Errorf("log level must be either %s, %s, %s or %s", ErrorLoggingLevel, WarnLoggingLevel, InfoLoggingLevel, DebugLoggingLevel), ) @@ -80,7 +79,7 @@ func (cfg TelemetryConfig) Validate() error { case AutoLoggingFormat, JsonLoggingFormat, TextLoggingFormat: break default: - err = multierr.Append( + err = errors.Join( err, fmt.Errorf("standard log format must be either %s, %s or %s", AutoLoggingFormat, JsonLoggingFormat, TextLoggingFormat), ) diff --git a/pkg/modules/api/api.go b/pkg/modules/api/api.go index ecc38b01..75053d5e 100644 --- a/pkg/modules/api/api.go +++ b/pkg/modules/api/api.go @@ -15,7 +15,6 @@ import ( "github.com/dlclark/regexp2" "github.com/labstack/echo/v4" flag "github.com/spf13/pflag" - "go.uber.org/multierr" "golang.org/x/net/http2" "golang.org/x/sync/errgroup" @@ -354,35 +353,35 @@ func (a *Api) Validate() error { var err error if a.port < 1 || a.port > 65535 { - err = multierr.Append(err, + err = errors.Join(err, errors.New("port must be more than 1 and less than 65535"), ) } if a.bindIp != "" && net.ParseIP(a.bindIp) == nil { - err = multierr.Append(err, errors.New("IP must be a valid IP address")) + err = errors.Join(err, errors.New("IP must be a valid IP address")) } if (a.tlsCertFile != "" && a.tlsKeyFile == "") || (a.tlsCertFile == "" && a.tlsKeyFile != "") { - err = multierr.Append(err, + err = errors.Join(err, errors.New("both TLS certificate and key files must be set"), ) } if !strings.HasPrefix(a.rootPath, "/") { - err = multierr.Append(err, + err = errors.Join(err, errors.New("root path must start with /"), ) } if !strings.HasSuffix(a.rootPath, "/") { - err = multierr.Append(err, + err = errors.Join(err, errors.New("root path must end with /"), ) } if len(strings.TrimSpace(a.correlationIdHeader)) == 0 { - err = multierr.Append(err, + err = errors.Join(err, errors.New("trace header must not be empty"), ) } diff --git a/pkg/modules/api/formdata.go b/pkg/modules/api/formdata.go index 007ecc10..d920e9f9 100644 --- a/pkg/modules/api/formdata.go +++ b/pkg/modules/api/formdata.go @@ -1,6 +1,7 @@ package api import ( + "errors" "fmt" "math" "net/http" @@ -12,8 +13,6 @@ import ( "strings" "time" - "go.uber.org/multierr" - "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" ) @@ -502,7 +501,7 @@ func (form *FormData) paths(extensions []string, target *[]string) *FormData { // append adds an error to the list of errors. func (form *FormData) append(err error) { - form.errors = multierr.Append(form.errors, err) + form.errors = errors.Join(form.errors, err) } // mustValue binds the target interface with a form field. If the value is diff --git a/pkg/modules/chromium/events.go b/pkg/modules/chromium/events.go index 5497a976..fdac71bb 100644 --- a/pkg/modules/chromium/events.go +++ b/pkg/modules/chromium/events.go @@ -2,6 +2,7 @@ package chromium import ( "context" + "errors" "fmt" "log/slog" "net/http" @@ -17,7 +18,6 @@ import ( "github.com/chromedp/cdproto/runtime" "github.com/chromedp/chromedp" "github.com/dlclark/regexp2" - "go.uber.org/multierr" "golang.org/x/sync/errgroup" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" @@ -231,7 +231,7 @@ func listenForEventResponseReceived( options.invalidResourceHttpStatusCodeMu.Lock() defer options.invalidResourceHttpStatusCodeMu.Unlock() - *options.invalidResourceHttpStatusCode = multierr.Append( + *options.invalidResourceHttpStatusCode = errors.Join( *options.invalidResourceHttpStatusCode, fmt.Errorf("%s - %d: %s", ev.Response.URL, ev.Response.Status, http.StatusText(int(ev.Response.Status))), ) @@ -334,7 +334,7 @@ func listenForEventLoadingFailed(ctx context.Context, logger *slog.Logger, optio // We are looking for common errors. // TODO: sufficient? - errors := []string{ + knownErrors := []string{ "net::ERR_CONNECTION_CLOSED", "net::ERR_CONNECTION_RESET", "net::ERR_CONNECTION_REFUSED", @@ -348,8 +348,8 @@ func listenForEventLoadingFailed(ctx context.Context, logger *slog.Logger, optio "net::ERR_FILE_NOT_FOUND", "net::ERR_HTTP2_PROTOCOL_ERROR", } - if !slices.Contains(errors, ev.ErrorText) { - logger.DebugContext(ctx, fmt.Sprintf("skip EventLoadingFailed: '%s' is not part of %+v", ev.ErrorText, errors)) + if !slices.Contains(knownErrors, ev.ErrorText) { + logger.DebugContext(ctx, fmt.Sprintf("skip EventLoadingFailed: '%s' is not part of %+v", ev.ErrorText, knownErrors)) return } @@ -378,7 +378,7 @@ func listenForEventLoadingFailed(ctx context.Context, logger *slog.Logger, optio options.resourceLoadingFailedMu.Lock() defer options.resourceLoadingFailedMu.Unlock() - *options.resourceLoadingFailed = multierr.Append( + *options.resourceLoadingFailed = errors.Join( *options.resourceLoadingFailed, fmt.Errorf("resource %s: %s", ev.Type, ev.ErrorText), ) @@ -397,7 +397,7 @@ func listenForEventExceptionThrown(ctx context.Context, logger *slog.Logger, con consoleExceptionsMu.Lock() defer consoleExceptionsMu.Unlock() - *consoleExceptions = multierr.Append(*consoleExceptions, fmt.Errorf("\n%+v", ev.ExceptionDetails)) + *consoleExceptions = errors.Join(*consoleExceptions, fmt.Errorf("\n%+v", ev.ExceptionDetails)) } }) } diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index 995091de..c0ee90c8 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -17,7 +17,6 @@ import ( "github.com/gomarkdown/markdown" "github.com/labstack/echo/v4" "github.com/microcosm-cc/bluemonday" - "go.uber.org/multierr" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" "github.com/gotenberg/gotenberg/v8/pkg/modules/api" @@ -150,7 +149,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) { for i, cookie := range cookies { if strings.TrimSpace(cookie.Name) == "" || strings.TrimSpace(cookie.Value) == "" || strings.TrimSpace(cookie.Domain) == "" { - err = multierr.Append(err, fmt.Errorf("cookie %d must have its name, value and domain set", i)) + err = errors.Join(err, fmt.Errorf("cookie %d must have its name, value and domain set", i)) } } @@ -183,7 +182,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) { if len(parts) == 2 && strings.ToLower(parts[0]) == "scope" && parts[1] != "" { scope = parts[1] } else { - err = multierr.Append(err, fmt.Errorf("invalid scope '%s' for header '%s'", scope, k)) + err = errors.Join(err, fmt.Errorf("invalid scope '%s' for header '%s'", scope, k)) invalidScopeToken = true break } @@ -200,7 +199,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) { if len(scope) > 0 { p, errCompile := regexp2.Compile(scope, regexp2.None) if errCompile != nil { - err = multierr.Append(err, fmt.Errorf("invalid scope regex pattern for header '%s': %w", k, errCompile)) + err = errors.Join(err, fmt.Errorf("invalid scope regex pattern for header '%s': %w", k, errCompile)) continue } scopeRegexp = p @@ -676,7 +675,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) } if path == "" { - markdownFilesNotFoundErr = multierr.Append( + markdownFilesNotFoundErr = errors.Join( markdownFilesNotFoundErr, fmt.Errorf("'%s'", filename), ) diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index b33051d3..2dd2a4ff 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -18,7 +18,6 @@ import ( "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.40.0" "go.opentelemetry.io/otel/trace" - "go.uber.org/multierr" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" "github.com/gotenberg/gotenberg/v8/pkg/modules/api" @@ -468,12 +467,12 @@ func (a *Api) Validate() error { _, statErr := os.Stat(a.args.binPath) if os.IsNotExist(statErr) { - err = multierr.Append(err, fmt.Errorf("LibreOffice binary path does not exist: %w", statErr)) + err = errors.Join(err, fmt.Errorf("LibreOffice binary path does not exist: %w", statErr)) } _, statErr = os.Stat(a.args.unoBinPath) if os.IsNotExist(statErr) { - err = multierr.Append(err, fmt.Errorf("unoconverter binary path does not exist: %w", statErr)) + err = errors.Join(err, fmt.Errorf("unoconverter binary path does not exist: %w", statErr)) } return err diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index f60f0415..0c58e4ec 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -2,13 +2,13 @@ package pdfengines import ( "context" + "errors" "fmt" "log/slog" "sync" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - "go.uber.org/multierr" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" ) @@ -80,8 +80,9 @@ func (multi *multiPdfEngines) Merge(ctx context.Context, logger *slog.Logger, in select { case mergeErr := <-errChan: - errored := multierr.AppendInto(&err, mergeErr) - if !errored { + if mergeErr != nil { + err = errors.Join(err, mergeErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -124,7 +125,7 @@ func (multi *multiPdfEngines) Split(ctx context.Context, logger *slog.Logger, mo case result := <-resultChan: if result.err != nil { mu.Lock() - err = multierr.Append(err, result.err) + err = errors.Join(err, result.err) mu.Unlock() } else { span.SetStatus(codes.Ok, "") @@ -159,8 +160,9 @@ func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *slog.Logger, select { case mergeErr := <-errChan: - errored := multierr.AppendInto(&err, mergeErr) - if !errored { + if mergeErr != nil { + err = errors.Join(err, mergeErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -193,8 +195,9 @@ func (multi *multiPdfEngines) Convert(ctx context.Context, logger *slog.Logger, select { case mergeErr := <-errChan: - errored := multierr.AppendInto(&err, mergeErr) - if !errored { + if mergeErr != nil { + err = errors.Join(err, mergeErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -239,7 +242,7 @@ func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *slog.Log case result := <-resultChan: if result.err != nil { mu.Lock() - err = multierr.Append(err, result.err) + err = errors.Join(err, result.err) mu.Unlock() } else { span.SetStatus(codes.Ok, "") @@ -274,8 +277,9 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *slog.Lo select { case writeMetadataErr := <-errChan: - errored := multierr.AppendInto(&err, writeMetadataErr) - if !errored { + if writeMetadataErr != nil { + err = errors.Join(err, writeMetadataErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -318,7 +322,7 @@ func (multi *multiPdfEngines) PageCount(ctx context.Context, logger *slog.Logger case result := <-resultChan: if result.err != nil { mu.Lock() - err = multierr.Append(err, result.err) + err = errors.Join(err, result.err) mu.Unlock() } else { span.SetStatus(codes.Ok, "") @@ -365,7 +369,7 @@ func (multi *multiPdfEngines) ReadBookmarks(ctx context.Context, logger *slog.Lo case result := <-resultChan: if result.err != nil { mu.Lock() - err = multierr.Append(err, result.err) + err = errors.Join(err, result.err) mu.Unlock() } else { span.SetStatus(codes.Ok, "") @@ -400,8 +404,9 @@ func (multi *multiPdfEngines) WriteBookmarks(ctx context.Context, logger *slog.L select { case writeBookmarksErr := <-errChan: - errored := multierr.AppendInto(&err, writeBookmarksErr) - if !errored { + if writeBookmarksErr != nil { + err = errors.Join(err, writeBookmarksErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -434,8 +439,9 @@ func (multi *multiPdfEngines) Encrypt(ctx context.Context, logger *slog.Logger, select { case protectErr := <-errChan: - errored := multierr.AppendInto(&err, protectErr) - if !errored { + if protectErr != nil { + err = errors.Join(err, protectErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -470,8 +476,9 @@ func (multi *multiPdfEngines) EmbedFiles(ctx context.Context, logger *slog.Logge select { case embedErr := <-errChan: - errored := multierr.AppendInto(&err, embedErr) - if !errored { + if embedErr != nil { + err = errors.Join(err, embedErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -506,8 +513,9 @@ func (multi *multiPdfEngines) Watermark(ctx context.Context, logger *slog.Logger select { case watermarkErr := <-errChan: - errored := multierr.AppendInto(&err, watermarkErr) - if !errored { + if watermarkErr != nil { + err = errors.Join(err, watermarkErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -542,8 +550,9 @@ func (multi *multiPdfEngines) Stamp(ctx context.Context, logger *slog.Logger, in select { case stampErr := <-errChan: - errored := multierr.AppendInto(&err, stampErr) - if !errored { + if stampErr != nil { + err = errors.Join(err, stampErr) + } else { span.SetStatus(codes.Ok, "") return nil } @@ -576,8 +585,9 @@ func (multi *multiPdfEngines) Rotate(ctx context.Context, logger *slog.Logger, i select { case rotateErr := <-errChan: - errored := multierr.AppendInto(&err, rotateErr) - if !errored { + if rotateErr != nil { + err = errors.Join(err, rotateErr) + } else { span.SetStatus(codes.Ok, "") return nil }