Skip to content

Error handling

In general

Whenever a middleware or handler returns a value which implements error interface, the execution flow is frozen and appropriate error handler is called. To create an error handler, you need to implement a function with the following rules:

  1. Takes exactly one argument, which type implements error interface.
  2. Can not return an error.
  3. Values returned from error handler behave similarly like from a middleware.
  4. Should return a response, because the handler might not be called.

The simplest example can look like:

func errorHandler(err error) (string, gnext.Status) {
    return err.Error(), 500
}

Register it in the router:

r.OnError(errorHandler)

Now every returned error will be handled by our custom error handler.

Default handler

If you don't register any error handler, the default one will be called, which gracefully handles the following errors:

  • *json.SyntaxError
  • *json.UnmarshalTypeError
  • validator.ValidationErrors
  • *gnext.NotFound

You probably noticed, that in documentation of your API, next to your response, there are error responses. They come from the default error handler definition which returns the following response struct:

type DefaultErrorResponse struct {
    ErrorResponse `default_status:"500" status_codes:"4XX,5XX"`
    Message       string   `json:"message"`
    Details       []string `json:"details"`
    Success       bool     `json:"success"`
}

There are 3 status codes defined in response above. That's why in your documentation there are 500, 4XX and 5XX status codes with the response scheme above.

Error response

In order to document error response scheme, you need to define a response structure. It will be similar to the one in default response. Example:

type MyResponse struct {
    Message       string   `json:"message"`
    Success       bool     `json:"success"`
}

HTTP status

You can define HTTP status returned together with the error response just inside a struct. To do that, you need to mark your struct as an error response and add default_status tag.

type MyResponse struct {
    gnext.ErrorResponse `default_status:"422"`
    Message             `json:"message"`
}

Now, you can simplify your error handler and don't return a status:

func errorHandler(err error) *MyResponse {
    return &MyResponse{Message: err.Error()}
}

gNext will document MyResponse scheme under 422 HTTP status code. It will also use it in response as a default status code. You can override it, by returning gnext.Status from handler:

func errorHandler(err error) (*MyResponse, gnext.Status) {
    response := &MyResponse{Message: err.Error()}
    if response.Message == "not found" {
        return response, 404
    }
    return response, 400
}

This will return 404 HTTP status code if the error message is not found. However, 404 code won't be documented. To add additional status codes to documentation, you need to write comma-separated statuses to status_codes tag of gnext.ErrorResponse:

type MyResponse struct {
    gnext.ErrorResponse `default_status:"422" status_codes:"400,404"`
    Message             `json:"message"`
}

Now you will see the error response with all three codes.

Warning

OpenAPI v3 scheme keeps responses in a map: status code -> response schema. This means, it is not possible to document more than one response scheme for one status code. If you register more than one response with the same status code, the random one will be exposed in documentation.