diff --git a/errors/callback.go b/errors/callback.go index af36285..7ea16cd 100644 --- a/errors/callback.go +++ b/errors/callback.go @@ -94,17 +94,36 @@ func StackTrace(err error) []log.EventStackTrace { } // ErrorMessage extracts a specified error response to be returned -// to the caller if present, otherwise returns the default error -// string +// to the caller if present, otherwise returns an empty string func ErrorMessage(err error) string { var rerr messager if errors.As(err, &rerr) { - if resp := rerr.Message(); resp != "" { - return resp + return rerr.Message() + } + + return "" +} + +// UnwrapErrorMessage is a callback function that allows you to extract +// an error message from an error. If the error message returned is an empty +// string, UnwrapErrorMessage will attempt to recursively unwrap the error +// until a non-empty string is returned. If no message is returned it will +// return the original error's error string as default. +func UnwrapErrorMessage(err error) string { + originalErr := err + + if msg := ErrorMessage(err); msg != "" { + return msg + } + + for errors.Unwrap(err) != nil { + if msg := ErrorMessage(err); msg != "" { + return msg } + err = errors.Unwrap(err) } - return err.Error() + return originalErr.Error() } // UnwrapStatusCode is a callback function that allows you to extract diff --git a/errors/callback_test.go b/errors/callback_test.go index 0aa0d70..04a293b 100644 --- a/errors/callback_test.go +++ b/errors/callback_test.go @@ -15,6 +15,7 @@ import ( type testError struct { err error statusCode int + message string logData map[string]interface{} } @@ -37,6 +38,10 @@ func (e testError) LogData() map[string]interface{} { return e.logData } +func (e testError) Message() string { + return e.message +} + func TestUnwrapLogDataHappy(t *testing.T) { Convey("Given an error with embedded logData", t, func() { @@ -237,3 +242,81 @@ func TestUnwrapStatusCodeHappy(t *testing.T) { }) } + +func TestUnwrapErrorMessageHappy(t *testing.T) { + + Convey("Given an error with embedded error message", t, func() { + err := &testError{ + message: "I am an error message", + } + + Convey("When ErrorMessage(err) is called", func() { + status := dperrors.ErrorMessage(err) + expected := "I am an error message" + + So(status, ShouldEqual, expected) + }) + }) + + Convey("Given an error chain with embedded error message", t, func() { + err1 := &testError{ + err: errors.New("original error"), + message: "I am embedded message", + } + + err2 := &testError{ + err: fmt.Errorf("err1: %w", err1), + } + + err3 := &testError{ + err: fmt.Errorf("err2: %w", err2), + } + + Convey("When UnwrapErrorMessage(err) is called", func() { + status := dperrors.UnwrapErrorMessage(err3) + expected := "I am embedded message" + + So(status, ShouldEqual, expected) + }) + }) + + Convey("Given an error chain with multiple embedded status codes", t, func() { + err1 := &testError{ + err: errors.New("original error"), + message: "I am embedded message", + } + + err2 := &testError{ + err: fmt.Errorf("err1: %w", err1), + message: "I am first embedded message", + } + + err3 := &testError{ + err: fmt.Errorf("err2: %w", err2), + } + + Convey("When UnwrapErrorMessage(err) is called", func() { + status := dperrors.UnwrapErrorMessage(err3) + expected := "I am first embedded message" + + Convey("The first valid error message is returned ", func() { + So(status, ShouldEqual, expected) + }) + }) + }) + + Convey("Given an error with no embedded error message", t, func() { + err := &testError{ + err: errors.New("original error"), + } + + Convey("When StatusCode(err) is called", func() { + status := dperrors.UnwrapErrorMessage(err) + expected := "original error" + + Convey("The orinal error string is returned ", func() { + So(status, ShouldEqual, expected) + }) + }) + }) +} diff --git a/responder/responder.go b/responder/responder.go index fd5a10b..a028df9 100644 --- a/responder/responder.go +++ b/responder/responder.go @@ -65,7 +65,7 @@ func respondError(ctx context.Context, w http.ResponseWriter, status int, err er status = http.StatusInternalServerError } - msg := dperrors.ErrorMessage(err) + msg := dperrors.UnwrapErrorMessage(err) resp := errorResponse{ Errors: []string{msg}, } @@ -108,7 +108,7 @@ func (r *Responder) Errors(ctx context.Context, w http.ResponseWriter, status in StackTrace: dperrors.StackTrace(err), Data: dperrors.UnwrapLogData(err), }) - errorMsgs = append(errorMsgs, dperrors.ErrorMessage(err)) + errorMsgs = append(errorMsgs, dperrors.UnwrapErrorMessage(err)) } log.Info(ctx, "error responding to HTTP request", log.ERROR, &errorLogs)