diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-07-06 09:00:24 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-07-06 14:00:01 +0300 |
commit | 23c5e74210aac0f360bb24cb2677bf37c559322f (patch) | |
tree | 652cea4b1d192a59bb82b33f65019c5ed17884d8 | |
parent | 4487da54883de88595e74d2a83d0afe605aca0a8 (diff) |
structerr: Provide convenience function to extract metadata from errors
Callers that wish to extract error metadata from a generic error need to
manually extract the `structerr.Error` first and then call `Metadata()`
on it, which is a bit cumbersome. Furthermore, we're about to extend the
logic to also allow for any other error types to carry metadata via an
interface, which would require them to also be on the lookout for any
such interface types.
Refactor the code and pull out the logic to extract error metadata into
a standalone `structerr.ExtractMetadata()` function. Given a generic
error, it will return all error metadata that is contained in the error
chain. This makes it easy to extend the logic going forward.
Adjust existing callsites to use this new function.
-rw-r--r-- | internal/structerr/error.go | 41 | ||||
-rw-r--r-- | internal/structerr/error_test.go | 1 | ||||
-rw-r--r-- | internal/structerr/grpc_server.go | 9 | ||||
-rw-r--r-- | internal/testhelper/testserver/structerr_interceptors.go | 9 |
4 files changed, 27 insertions, 33 deletions
diff --git a/internal/structerr/error.go b/internal/structerr/error.go index 184b0da90..786522516 100644 --- a/internal/structerr/error.go +++ b/internal/structerr/error.go @@ -269,24 +269,9 @@ func (e Error) errorChain() []Error { return result } -// Metadata returns the Error's metadata. 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 -// callchain one is the more context is available. +// Metadata returns the Error's metadata. Please refer to `ExtractMetadata()` for the exact semantics of this function. func (e Error) Metadata() map[string]any { - result := map[string]any{} - - for _, err := range e.errorChain() { - for _, m := range err.metadata { - if _, exists := result[m.Key]; !exists { - result[m.Key] = m.Value - } - } - } - - return result + return ExtractMetadata(e) } // MetadataItems returns a copy of all metadata items added to this error. This function has the @@ -356,3 +341,25 @@ func (e Error) WithGRPCCode(code codes.Code) Error { e.code = code 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. +// +// 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 +// callchain one is the more context is available. +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 + } + } + } + } + + return metadata +} diff --git a/internal/structerr/error_test.go b/internal/structerr/error_test.go index c1a65b689..dc8852977 100644 --- a/internal/structerr/error_test.go +++ b/internal/structerr/error_test.go @@ -338,6 +338,7 @@ func TestError_Metadata(t *testing.T) { expectedItemsByKey[item.Key] = item.Value } require.Equal(t, expectedItemsByKey, err.Metadata()) + require.Equal(t, expectedItemsByKey, ExtractMetadata(err)) } t.Run("without metadata", func(t *testing.T) { diff --git a/internal/structerr/grpc_server.go b/internal/structerr/grpc_server.go index 367f149e7..d2f565849 100644 --- a/internal/structerr/grpc_server.go +++ b/internal/structerr/grpc_server.go @@ -2,7 +2,6 @@ package structerr import ( "context" - "errors" "github.com/sirupsen/logrus" ) @@ -10,13 +9,7 @@ import ( // FieldsProducer extracts metadata from err if it contains a `structerr.Error` and exposes it as // logged fields. This function is supposed to be used with `log.MessageProducer()`. func FieldsProducer(_ context.Context, err error) logrus.Fields { - var structErr Error - if errors.As(err, &structErr) { - metadata := structErr.Metadata() - if len(metadata) == 0 { - return nil - } - + if metadata := ExtractMetadata(err); len(metadata) > 0 { return logrus.Fields{ "error_metadata": metadata, } diff --git a/internal/testhelper/testserver/structerr_interceptors.go b/internal/testhelper/testserver/structerr_interceptors.go index 26b53d63c..6c4ffb4b1 100644 --- a/internal/testhelper/testserver/structerr_interceptors.go +++ b/internal/testhelper/testserver/structerr_interceptors.go @@ -2,7 +2,6 @@ package testserver import ( "context" - "errors" "sort" "gitlab.com/gitlab-org/gitaly/v16/internal/structerr" @@ -26,13 +25,7 @@ func StructErrStreamInterceptor(srv interface{}, stream grpc.ServerStream, info } func interceptedError(err error) error { - var structErr structerr.Error - if errors.As(err, &structErr) { - metadata := structErr.Metadata() - if len(metadata) == 0 { - return err - } - + if metadata := structerr.ExtractMetadata(err); len(metadata) > 0 { keys := make([]string, 0, len(metadata)) for key := range metadata { keys = append(keys, key) |