Error handling
Error handling is a crucial part of any application. It is important to handle errors gracefully and provide meaningful feedback to the user. In this guide, we will cover how to handle errors in a Fuego application.
Default error handling in Fuego
Fuego controllers returns a value and an error. If the error is not nil
,
it means that an error occurred while processing the request.
The error will be returned to the client as a JSON or XML response, and logged to the console.
func MyController(c fuego.ContextNoBody) (string, error) {
return "", errors.New("an error occurred") // Sends a 500. Will be logged but not returned to the client as it is not serializable.
}
You are encouraged to use provided fuego error types, that implement RFC 9457, which defines a standard error format for HTTP APIs.
func MyController(c fuego.ContextNoBody) (string, error) {
_, err := someFunction()
if err != nil {
return "", fuego.BadRequestError{Title: "You cannot do that", Err: err} // Returns and logs a structured 400 error.
}
return "success", nil
}
fuego.BadRequestError
: 400 Bad Requestfuego.UnauthorizedError
: 401 Unauthorizedfuego.ForbiddenError
: 403 Forbiddenfuego.NotFoundError
: 404 Not Foundfuego.NotAcceptableError
: 406 Not Acceptablefuego.ConflictError
: 409 Conflictfuego.InternalServerError
: 500 Internal Server Error
Custom error types
The default error handler will transform any error that implements the
fuego.ErrorWithStatus
interfaces into a fuego.HTTPError
(that implements
RFC 9457).
The error type of fuego.HTTPError
is returned as JSON or XML depending on the Accept
header specified.
It's structure is the following:
type ErrorWithStatus interface {
error
StatusCode() int
}
Example:
type MyCustomError struct {
Err error `json:"error"`
Message string `json:"message"`
}
var (
_ fuego.ErrorWithStatus = MyCustomError{}
_ fuego.ErrorWithDetail = MyCustomError{}
)
func (e MyCustomError) Error() string { return e.Err.Error() }
func (e MyCustomError) StatusCode() int { return http.StatusTeapot }
func (e MyCustomError) DetailMsg() string {
return strings.Split(e.Error(), " ")[1]
}
Alternatively, you can always use fuego.HTTPError
directly such as:
err := fuego.HTTPError{
Title: "Custom error",
Detail: "This is a custom error",
Status: http.StatusTeapot,
}
Custom error handling
The default fuego.ErrorHandler
can be overridden using fuego.WithErrorHandler
at fuego Engine
creation time. Example mapping sqlite errors to HTTP errors.
import (
"errors"
"modernc.org/sqlite"
"github.com/go-fuego/fuego"
)
func sqliteErrorHandler(err error) error {
var sqliteError *sqlite.Error
if errors.As(err, &sqliteError) {
sqliteErrorCode := sqliteError.Code()
switch sqliteErrorCode {
case 1555, 2067 /* UNIQUE constraint failed */ :
return fuego.ConflictError{Title: "Duplicate", Detail: sqliteError.Error(), Err: sqliteError}
default:
return fuego.InternalServerError{Title: "Internal Server Error", Detail: sqliteError.Error(), Err: sqliteError}
}
}
return err
}
// apply all error handlers that we want, like a chain of middlewares
func customErrorHandler(err error) error {
return fuego.ErrorHandler(sqliteErrorHandler(myOtherCustomErrorHandler(err)))
}
func main() {
s := fuego.NewServer(
fuego.WithEngineOptions(
fuego.WithErrorHandler(customErrorHandler))
),
)
fuego.Get(s, "/", MyController)
s.Run()
}