-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from ing-bank/v2
v2 concept
- Loading branch information
Showing
10 changed files
with
751 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.idea/ | ||
.vscode/ | ||
|
||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2022 ING | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
MAKEFLAGS := --no-print-directory --silent | ||
|
||
default: help | ||
|
||
help: | ||
@echo "Please use 'make <target>' where <target> is one of" | ||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z\._-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | ||
|
||
t: test | ||
test: fmt ## Run unit tests, alias: t | ||
go test ./... -timeout=30s -parallel=8 | ||
|
||
fmt: ## Format go code | ||
@go mod tidy | ||
@go fmt ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# 🦁 Gin Error Registry | ||
|
||
[](https://github.com/ing-bank/ginerr/actions/workflows/test.yaml) | ||
 | ||
 | ||
|
||
Sending any error back to the user can pose a [big security risk](https://owasp.org/www-community/Improper_Error_Handling). | ||
For this reason we developed an error registry that allows you to register specific error handlers | ||
for your application. This way you can control what information is sent back to the user. | ||
|
||
You can register errors in 3 ways: | ||
- By error type | ||
- By value of string errors | ||
- By defining the error name yourself | ||
|
||
## 👷 V2 migration guide | ||
|
||
V2 of this library changes the interface of all the methods to allow contexts to be passed to handlers. This | ||
allows you to add additional data to the final response. | ||
|
||
The interface changes are as follows. | ||
|
||
- `RegisterErrorHandler` and all its variants take a context as a first parameter in the handler, allowing you to pass more data to the response | ||
- `RegisterErrorHandler` and all its variants require the callback function to return `(int, any)` instead of `(int, R)`, removing the unnecessary generic | ||
- Both `NewErrorResponse` and `NewErrorResponseFrom` take a context as a first parameter, this could be the request context but that's up to you | ||
|
||
## ⬇️ Installation | ||
|
||
`go get github.com/ing-bank/ginerr/v2` | ||
|
||
## 📋 Usage | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"github.com/gin-gonic/gin" | ||
"github.com/ing-bank/ginerr/v2" | ||
"net/http" | ||
) | ||
|
||
type MyError struct { | ||
} | ||
|
||
func (m *MyError) Error() string { | ||
return "Something went wrong!" | ||
} | ||
|
||
// Response is an example response object, you can return anything you like | ||
type Response struct { | ||
Errors map[string]any `json:"errors,omitempty"` | ||
} | ||
|
||
func main() { | ||
handler := func(ctx context.Context, myError *MyError) (int, any) { | ||
return http.StatusInternalServerError, Response{ | ||
Errors: map[string]any{ | ||
"error": myError.Error(), | ||
}, | ||
} | ||
} | ||
|
||
ginerr.RegisterErrorHandler(handler) | ||
|
||
// [...] | ||
} | ||
|
||
func handleGet(c *gin.Context) { | ||
err := &MyError{} | ||
c.JSON(ginerr.NewErrorResponse(c.Request.Context(), err)) | ||
} | ||
``` | ||
|
||
## 🚀 Development | ||
|
||
1. Clone the repository | ||
2. Run `make t` to run unit tests | ||
3. Run `make fmt` to format code | ||
|
||
You can run `make` to see a list of useful commands. | ||
|
||
## 🔭 Future Plans | ||
|
||
Nothing here yet! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package ginerr | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
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), | ||
stringHandlers: make(map[string]internalStringHandler), | ||
DefaultCode: defaultCode, | ||
} | ||
|
||
// Make sure the stringHandlers are available in the handlers | ||
registry.handlers["*errors.errorString"] = func(ctx context.Context, err error) (int, any) { | ||
// Check if the error string exists | ||
if handler, ok := registry.stringHandlers[err.Error()]; ok { | ||
return handler(ctx, err.Error()) | ||
} | ||
|
||
return registry.DefaultCode, registry.DefaultResponse | ||
} | ||
|
||
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 | ||
DefaultCode int | ||
|
||
// DefaultResponse to return when no handler is found | ||
DefaultResponse any | ||
} | ||
|
||
func (e *ErrorRegistry) SetDefaultResponse(code int, response any) { | ||
e.DefaultCode = code | ||
e.DefaultResponse = response | ||
} | ||
|
||
// 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) { | ||
return NewErrorResponseFrom(DefaultErrorRegistry, ctx, err) | ||
} | ||
|
||
// NewErrorResponseFrom Returns an error response using the given registry. If no specific handler could be found, | ||
// it will return the defaults. | ||
func NewErrorResponseFrom(registry *ErrorRegistry, ctx context.Context, err error) (int, any) { | ||
errorType := fmt.Sprintf("%T", err) | ||
|
||
// If a handler is registered for the error type, use it. | ||
if entry, ok := registry.handlers[errorType]; ok { | ||
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 | ||
} | ||
|
||
// RegisterErrorHandler registers an error handler in DefaultErrorRegistry. The R type is the type of the response body. | ||
func RegisterErrorHandler[E error](handler func(context.Context, E) (int, any)) { | ||
RegisterErrorHandlerOn(DefaultErrorRegistry, handler) | ||
} | ||
|
||
// RegisterErrorHandlerOn registers an error handler in the given registry. The R type is the type of the response body. | ||
func RegisterErrorHandlerOn[E error](registry *ErrorRegistry, handler func(context.Context, E) (int, any)) { | ||
// Name of the type | ||
errorType := fmt.Sprintf("%T", *new(E)) | ||
|
||
// Wrap it in a closure, we can't save it directly because err E is not available in NewErrorResponseFrom. It will | ||
// be available in the closure when it is called. Check out TestErrorResponseFrom_ReturnsErrorBInInterface for an example. | ||
registry.handlers[errorType] = func(ctx context.Context, err error) (int, any) { | ||
return handler(ctx, err.(E)) | ||
} | ||
} | ||
|
||
// RegisterCustomErrorTypeHandler registers an error handler in DefaultErrorRegistry. Same as RegisterErrorHandler, | ||
// but you can set the fmt.Sprint("%T", err) error yourself. Allows you to register error types that aren't exported | ||
// from their respective packages such as the uuid error or *errors.errorString. The R type is the type of the response body. | ||
func RegisterCustomErrorTypeHandler(errorType string, handler func(ctx context.Context, err error) (int, any)) { | ||
RegisterCustomErrorTypeHandlerOn(DefaultErrorRegistry, errorType, handler) | ||
} | ||
|
||
// RegisterCustomErrorTypeHandlerOn registers an error handler in the given registry. Same as RegisterErrorHandlerOn, | ||
// but you can set the fmt.Sprint("%T", err) error yourself. Allows you to register error types that aren't exported | ||
// from their respective packages such as the uuid error or *errors.errorString. The R type is the type of the response body. | ||
func RegisterCustomErrorTypeHandlerOn(registry *ErrorRegistry, errorType string, handler func(ctx context.Context, err error) (int, any)) { | ||
// Wrap it in a closure, we can't save it directly | ||
registry.handlers[errorType] = handler | ||
} | ||
|
||
// RegisterStringErrorHandler allows you to register an error handler for a simple errorString created with | ||
// errors.New() or fmt.Errorf(). Can be used in case you are dealing with libraries that don't have exported | ||
// error objects. Uses the DefaultErrorRegistry. The R type is the type of the response body. | ||
func RegisterStringErrorHandler(errorString string, handler func(ctx context.Context, err string) (int, any)) { | ||
RegisterStringErrorHandlerOn(DefaultErrorRegistry, errorString, handler) | ||
} | ||
|
||
// RegisterStringErrorHandlerOn allows you to register an error handler for a simple errorString created with | ||
// errors.New() or fmt.Errorf(). Can be used in case you are dealing with libraries that don't have exported | ||
// error objects. The R type is the type of the response body. | ||
func RegisterStringErrorHandlerOn(registry *ErrorRegistry, errorString string, handler func(ctx context.Context, err string) (int, any)) { | ||
registry.stringHandlers[errorString] = handler | ||
} |
Oops, something went wrong.