From 8cd90e03f83409efcbe15bc082d7792815e1ab10 Mon Sep 17 00:00:00 2001 From: Kinane Date: Thu, 23 May 2019 12:59:37 +0300 Subject: [PATCH] Enhance error handling - Wraps panic recovery within the handler - Abstract status definition from error instantiation and centralises it in the handler --- README.md | 17 ++++++++++------- routes/serve.go | 8 ++------ throw/error.go | 10 ++++++++++ throw/errors.go | 32 -------------------------------- throw/handler.go | 33 +++++++++++++++++++++++++-------- throw/invalid-input.go | 14 ++++++++++++++ 6 files changed, 61 insertions(+), 53 deletions(-) create mode 100644 throw/error.go delete mode 100644 throw/errors.go create mode 100644 throw/invalid-input.go diff --git a/README.md b/README.md index 3f82a07..f659981 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ go get gopkg.in/go-playground/validator.v9 ``` go get github.com/gorilla/schema ``` -- Use the following snippet to parse and validate given input as a struct (see [here](#errors) for errors) +- Use the following snippet to parse and validate given input as a struct (see [here](#invalid-input)) ```go type ListingInput struct { Limit uint `schema:"limit" validate:"min=0,max=100"` @@ -48,7 +48,7 @@ func ParseListingInput(r *http.Request) *ListingInput { func Validate(input *ListingInput) { if err := validator.New().Struct(input); err != nil { - panic(throw.InvalidInputError(err.Error(), 1000, 400)) + panic(throw.InvalidInputError(err.Error(), 1000)) } } ``` @@ -69,15 +69,18 @@ response.JSONError(w, status, errorCode, errorMessage) ``` ### Error Handling -- Define errors in the `throw` package at `throw/errors.go` as a struct +- Define errors in the `throw` package + ```go -type SomethingWrong struct { - *HTTPError +package throw + +type NewError struct { + BaseError } ``` - For each error, better have a factory method to help make a new instance of the error, i.e. ```go -func SomethingWrongError(m string, c, s uint16) SomethingWrong { - return InvalidInput{&HTTPError{Message: m, Code: c, Status: s}r} +func SomethingWrongError(m string, c uint16) SomethingWrong { + return InvalidInput{&HTTPError{Message: m, Code: c}} } ``` diff --git a/routes/serve.go b/routes/serve.go index 845444e..1ca838c 100644 --- a/routes/serve.go +++ b/routes/serve.go @@ -3,18 +3,14 @@ package routes import ( "net/http" - "github.com/mulkave/go-init/throw" + "github.com/vinelab/go-init/throw" ) // Serve wraps the features to be served from the routes, // implements centralised error handling. func Serve(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - defer func() { - if e := recover(); e != nil { - throw.Handle(e.(throw.Error), w, r) - } - }() + defer throw.HandleHttpRequestErrors(w) handler(w, r) } diff --git a/throw/error.go b/throw/error.go new file mode 100644 index 0000000..2fe2d04 --- /dev/null +++ b/throw/error.go @@ -0,0 +1,10 @@ +package throw + +type BaseError struct { + Message string + Code uint16 +} + +func (e BaseError) Error() string { + return e.Message +} diff --git a/throw/errors.go b/throw/errors.go deleted file mode 100644 index 9eef717..0000000 --- a/throw/errors.go +++ /dev/null @@ -1,32 +0,0 @@ -package throw - -// Error is what we should be handling throught the application -type Error interface { - error -} - -// HTTPError is the base error for the errors that are -// thrown among the HTTP context -type HTTPError struct { - Message string - Code uint16 - Status uint16 -} - -// InvalidInput specifies that the received input parameters -// are not as expected and have failed the validation -type InvalidInput struct { - *HTTPError -} - -// Error is implemented to comply with the "error" interface -// specified by Exception. -func (e InvalidInput) Error() string { - return e.Message -} - -// InvalidInputError creates and returns a new InvalidInput instance -// with the given data. -func InvalidInputError(m string, c, s uint16) InvalidInput { - return InvalidInput{&HTTPError{Message: m, Code: c, Status: s}} -} diff --git a/throw/handler.go b/throw/handler.go index eec9436..20e9182 100644 --- a/throw/handler.go +++ b/throw/handler.go @@ -1,12 +1,29 @@ package throw -import "net/http" - -// Handle panics, it prefers to be deferred. -func Handle(e Error, w http.ResponseWriter, r *http.Request) { - switch e.(type) { - case InvalidInput: - err := e.(InvalidInput) // cast to error type - http.Error(w, err.Message, int(err.Status)) +import ( + "net/http" + + "github.com/vinelab/go-init/response" +) + +func HandleHttpRequestErrors(w http.ResponseWriter) { + + //check if panic happened + if err := recover(); err != nil { + var status uint16 + var code uint16 + + switch err.(type) { + case InvalidInput: + status = 400 + + err := err.(InvalidInput) + code = err.Code + default: + status = 500 + code = 500 + } + + response.JSONError(w, status, code, err.(error).Error()) } } diff --git a/throw/invalid-input.go b/throw/invalid-input.go new file mode 100644 index 0000000..0a59413 --- /dev/null +++ b/throw/invalid-input.go @@ -0,0 +1,14 @@ +package throw + +// InvalidInput specifies that the received input parameters +type InvalidInput struct { + *BaseError +} + +func (e InvalidInput) Error() string { + return "Invalid input error: " + e.BaseError.Error() +} + +func InvalidInputError(m string, c uint16) InvalidInput { + return InvalidInput{&BaseError{Message: m, Code: c}} +}