Skip to content

Commit

Permalink
Merge pull request #3 from ing-bank/v2
Browse files Browse the repository at this point in the history
Add DefaultHandler as an option
  • Loading branch information
survivorbat authored Aug 16, 2023
2 parents a97a032 + 2537e57 commit abe91c6
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 15 deletions.
3 changes: 0 additions & 3 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

const defaultCode = http.StatusInternalServerError

// DefaultErrorRegistry is the default ErrorRegistry for the application, can be overridden for rare use-cases.
var DefaultErrorRegistry = NewErrorRegistry()

type internalHandler func(err error) (int, any)
Expand All @@ -25,7 +24,6 @@ type ErrorStringHandler[R any] func(err string) (int, R)
// the handler is registered for. The R type is the type of the response body.
type ErrorHandler[E error, R any] func(E) (int, R)

// NewErrorRegistry is ideal for testing or overriding the default one.
func NewErrorRegistry() *ErrorRegistry {
registry := &ErrorRegistry{
handlers: make(map[string]internalHandler),
Expand All @@ -46,7 +44,6 @@ func NewErrorRegistry() *ErrorRegistry {
return registry
}

// ErrorRegistry contains a map of ErrorHandlers.
type ErrorRegistry struct {
// handlers are used when we know the type of the error
handlers map[string]internalHandler
Expand Down
31 changes: 22 additions & 9 deletions v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import (

const defaultCode = http.StatusInternalServerError

// DefaultErrorRegistry is the default ErrorRegistry for the application, can be overridden for rare use-cases.
var DefaultErrorRegistry = NewErrorRegistry()

type internalHandler func(ctx context.Context, err error) (int, any)
type internalStringHandler func(ctx context.Context, err string) (int, any)

// NewErrorRegistry is ideal for testing or overriding the default one.
func NewErrorRegistry() *ErrorRegistry {
registry := &ErrorRegistry{
handlers: make(map[string]internalHandler),
Expand All @@ -29,32 +27,49 @@ func NewErrorRegistry() *ErrorRegistry {
return handler(ctx, err.Error())
}

return registry.DefaultCode, registry.DefaultResponse
return registry.defaultResponse(ctx, err)
}

return registry
}

// ErrorRegistry contains a map of ErrorHandlers.
type ErrorRegistry struct {
// handlers are used when we know the type of the error
handlers map[string]internalHandler

// stringHandlers are used when the error is only a string
stringHandlers map[string]internalStringHandler

// DefaultCode to return when no handler is found
// DefaultHandler takes precedent over DefaultCode and DefaultResponse
DefaultHandler func(ctx context.Context, err error) (int, any)

// DefaultCode to return when no handler is found. Deprecated: Prefer DefaultHandler
DefaultCode int

// DefaultResponse to return when no handler is found
// DefaultResponse to return when no handler is found. Deprecated: Prefer DefaultHandler
DefaultResponse any
}

// SetDefaultResponse is deprecated, prefer RegisterDefaultHandler
func (e *ErrorRegistry) SetDefaultResponse(code int, response any) {
e.DefaultCode = code
e.DefaultResponse = response
}

func (e *ErrorRegistry) RegisterDefaultHandler(callback func(ctx context.Context, err error) (int, any)) {
e.DefaultHandler = callback
}

func (e *ErrorRegistry) defaultResponse(ctx context.Context, err error) (int, any) {
// In production, we should return a generic error message. If you want to know why, read this:
// https://owasp.org/www-community/Improper_Error_Handling
if e.DefaultHandler != nil {
return e.DefaultHandler(ctx, err)
}

return e.DefaultCode, e.DefaultResponse
}

// NewErrorResponse Returns an error response using the DefaultErrorRegistry. If no specific handler could be found,
// it will return the defaults.
func NewErrorResponse(ctx context.Context, err error) (int, any) {
Expand All @@ -71,9 +86,7 @@ func NewErrorResponseFrom(registry *ErrorRegistry, ctx context.Context, err erro
return entry(ctx, err)
}

// In production, we should return a generic error message. If you want to know why, read this:
// https://owasp.org/www-community/Improper_Error_Handling
return registry.DefaultCode, registry.DefaultResponse
return registry.defaultResponse(ctx, err)
}

// RegisterErrorHandler registers an error handler in DefaultErrorRegistry. The R type is the type of the response body.
Expand Down
38 changes: 35 additions & 3 deletions v2/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,38 @@ func TestErrorResponseFrom_ReturnsGenericErrorOnNotFound(t *testing.T) {
assert.Equal(t, "test", response)
}

func TestErrorResponseFrom_UsesDefaultCallbackOnNotFound(t *testing.T) {
t.Parallel()
// Arrange
registry := NewErrorRegistry()

expectedResponse := Response{
Errors: map[string]any{"error": "internal server error"},
}

var calledWithErr error
var calledWithCtx context.Context
callback := func(ctx context.Context, err error) (int, any) {
calledWithErr = err
calledWithCtx = ctx
return http.StatusInternalServerError, expectedResponse
}

registry.RegisterDefaultHandler(callback)

ctx := context.WithValue(context.Background(), ErrorA{}, "good")

// Act
code, response := NewErrorResponseFrom(registry, ctx, assert.AnError)

// Assert
assert.Equal(t, expectedResponse, response)
assert.Equal(t, code, http.StatusInternalServerError)

assert.Equal(t, ctx, calledWithCtx)
assert.Equal(t, assert.AnError, calledWithErr)
}

func TestErrorResponseFrom_ReturnsErrorAWithContext(t *testing.T) {
t.Parallel()
// Arrange
Expand All @@ -141,7 +173,7 @@ func TestErrorResponseFrom_ReturnsErrorAWithContext(t *testing.T) {
callback := func(ctx context.Context, err *ErrorA) (int, any) {
calledWithErr = err
calledWithCtx = ctx
return 500, expectedResponse
return http.StatusInternalServerError, expectedResponse
}

err := &ErrorA{message: "It was the man with one hand!"}
Expand Down Expand Up @@ -172,7 +204,7 @@ func TestErrorResponseFrom_ReturnsErrorB(t *testing.T) {
var calledWithErr *ErrorB
callback := func(ctx context.Context, err *ErrorB) (int, any) {
calledWithErr = err
return 500, expectedResponse
return http.StatusInternalServerError, expectedResponse
}

err := &ErrorB{message: "It was the man with one hand!"}
Expand Down Expand Up @@ -200,7 +232,7 @@ func TestErrorResponseFrom_ReturnsErrorBInInterface(t *testing.T) {
var calledWithErr error
callback := func(ctx context.Context, err *ErrorB) (int, any) {
calledWithErr = err
return 500, expectedResponse
return http.StatusInternalServerError, expectedResponse
}

var err error = &ErrorB{message: "It was the man with one hand!"}
Expand Down

0 comments on commit abe91c6

Please sign in to comment.