diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-07-06 09:06:10 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-07-06 14:00:01 +0300 |
commit | 9f7b469b4c2944c0bb057a79e9e2c28aa2d283e1 (patch) | |
tree | 4e9d31f8fbd9e460d5085b26f5414c7f172b7abd | |
parent | 23c5e74210aac0f360bb24cb2677bf37c559322f (diff) |
structerr: Allow addding metadata to any error type
In order to add metadata to an error one must create a `structerr.Error`
right now. This is a bit roundabout though in the case where you already
have a custom error type that you wish to add metadata to. While you can
play games and implement an `Unwrap()` function on your error type that
returns a `structerr.Error` with metadata, this is indeed quite tedious
and roundabout.
Instead, provide a new `structerr.ErrorMetadater` interface that can be
implemented by custom error types. This allows them to attach metadata
to the custom error type directly via a `ErrorMetadata()` function.
-rw-r--r-- | internal/structerr/error.go | 29 | ||||
-rw-r--r-- | internal/structerr/error_test.go | 24 |
2 files changed, 46 insertions, 7 deletions
diff --git a/internal/structerr/error.go b/internal/structerr/error.go index 786522516..048258b6b 100644 --- a/internal/structerr/error.go +++ b/internal/structerr/error.go @@ -342,8 +342,16 @@ func (e Error) WithGRPCCode(code codes.Code) Error { return e } -// ExtractMetadata extracts metadata from the given error if any of the errors in its chain contain any. The metadata -// will contain the combination of all added metadata of this error as well as any wrapped Errors. +// ErrorMetadater is an interface that can be implemented by error types in order to provide custom metadata items +// without itself being a `structerr.Error`. +type ErrorMetadater interface { + // ErrorMetadata returns the list of metadata items attached to this error. + ErrorMetadata() []MetadataItem +} + +// ExtractMetadata extracts metadata from the given error if any of the errors in its chain contain any. Errors may +// contain in case they are either a `structerr.Error` or in case they implement the `ErrorMetadater` interface. The +// metadata will contain the combination of all added metadata of this error as well as any wrapped Errors. // // When the same metada key exists multiple times in the error chain, then the value that is // highest up the callchain will be returned. This is done because in general, the higher up the @@ -352,11 +360,18 @@ func ExtractMetadata(err error) map[string]any { metadata := map[string]any{} for ; err != nil; err = errors.Unwrap(err) { - if structErr, ok := err.(Error); ok { - for _, m := range structErr.metadata { - if _, exists := metadata[m.Key]; !exists { - metadata[m.Key] = m.Value - } + var metadataItems []MetadataItem + if structerr, ok := err.(Error); ok { + metadataItems = structerr.metadata + } else if errorMetadater, ok := err.(ErrorMetadater); ok { + metadataItems = errorMetadater.ErrorMetadata() + } else { + continue + } + + for _, m := range metadataItems { + if _, exists := metadata[m.Key]; !exists { + metadata[m.Key] = m.Value } } } diff --git a/internal/structerr/error_test.go b/internal/structerr/error_test.go index dc8852977..a998e5329 100644 --- a/internal/structerr/error_test.go +++ b/internal/structerr/error_test.go @@ -325,6 +325,18 @@ func TestError_Is(t *testing.T) { } } +type customWithMetadataError struct{} + +func (customWithMetadataError) Error() string { + return "custom error" +} + +func (customWithMetadataError) ErrorMetadata() []MetadataItem { + return []MetadataItem{ + {Key: "custom", Value: "error"}, + } +} + func TestError_Metadata(t *testing.T) { t.Parallel() @@ -479,6 +491,18 @@ func TestError_Metadata(t *testing.T) { {Key: "b", Value: "b"}, }) }) + + t.Run("custom type with metadata", func(t *testing.T) { + err := New("top-level: %w", customWithMetadataError{}) + + require.Equal(t, Error{ + err: fmt.Errorf("top-level: %w", customWithMetadataError{}), + code: codes.Unknown, + }, err) + requireItems(t, err, []MetadataItem{ + {Key: "custom", Value: "error"}, + }) + }) } func TestError_Details(t *testing.T) { |