# How do we handle error? In Go, the general approach is that every func should return an error so we can handle it immediatly. That is not the case when it comes to HTTP handlers. ``` func(http.ResponseWriter, *http.Request) ``` That cause our code to have logs all over the handler code since each error is logged and handled. ### Can we do better? What if our HTTP handler returned an error? ``` func(http.ResponseWriter, *http.Request) error ``` In that case, Error handling will be delegated to a centric place, where we can do all logging too. ### How should our handler act upon error? All inner calls should return an error to our handler. Upon an error, our handler should decide how to act. We have mapped the current possible acts: - abort with error - abort with status - abort with status JSON - redirect We will create a dedicated error for each possible handler act. Upon error, the handler will return one of those: ``` type AbortError struct { Code int Err error } type StatusError struct { Code int Err error } type JSONError struct { Code int Message string Details map[string]interface{} Err error } type RedirectError struct { Code int Location string Err error } ``` A typical handler, can call other packages like a db pkg. In case the other package code has an error we can decide to: - simply return the error back to the handler, - wrap the error back to the handler - wrap it into a custom `AppError` For the 3rd option, We will create a custom `AppError` error that would be used by the packages that the handler call to. ``` type AppError struct { code string message string err error } ``` ### Example ``` func TestJSONError(t *testing.T) { // simulate a db error dbErr := errors.New("SQL_ERR::NO_ROWS") // handler wraps the db error and constructs a JSONError... handlerErr := apperror.JSONError{Code: 401, Err: dbErr, Message: "user not found", Details: gin.H{"k1": "v1", "k2": 2}} assert.True(t, apperror.IsJSONResponder(handlerErr)) assert.Equal(t, map[string]interface{}{"k1": "v1", "k2": 2}, handlerErr.JSONDetails()) assert.Equal(t, "user not found. SQL_ERR::NO_ROWS", handlerErr.Error()) } ``` Since the handler returns a JSONError (which implements the `JSONResponder` interface), Our handler wrapper will now handle ths error: ``` func MakeHandlerFunc(f types.APIFunc) gin.HandlerFunc { return func(c *gin.Context) { if err := f(c); err != nil { log := log.Get(c.Request.Context()) . . . if jsonErr, ok := err.(apperror.JSONResponder); ok { //msg := fmt.Sprintf("[%v] - %v", ShortOperationID(c.Request.Context()), apperror.ErrorChain(jsonErr)) code, jsonObj := jsonErr.JSONResponse() msg := fmt.Sprintf("[%v] - (%d) aborting with json", ShortOperationID(c.Request.Context()), code) log.Error(jsonErr, msg, "details", jsonErr.JSONDetails()) c.AbortWithStatusJSON(code, jsonObj) return } . . . // handle non AppError log.Error(err, "unexpected error occurred") c.AbortWithError(500, errors.New("unexpected error occurred")) //nolint:errcheck return } } } ```