Skip to content

Commit

Permalink
chore: update error handling docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanhitt committed Dec 6, 2024
1 parent f2cd1ec commit b39acd7
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 14 deletions.
80 changes: 72 additions & 8 deletions documentation/docs/guides/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,85 @@ Error handling is a crucial part of any application. It is important to handle e

## Error handling in Fuego

Fuego [controllers](./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 response.
Fuego [controllers](./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.

By default, Fuego implements [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), which defines a standard error format for HTTP APIs. We strongly recommend following this standard, but you can also use your own errors.
The default error handler will transform any error that implements the
`fuego.ErrorWithStatus` or `fuego.ErrorWithDetail` interfaces into a `fuego.HTTPError`. The `fuego.HTTPError` implements
[RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), which defines a standard error format for HTTP APIs.
We strongly recommend following this standard, but you can also use your own errors.

The error type returned as JSON is `fuego.HTTPError`. It has a `Status` and a `Info` field. The `Status` field is an integer that represents the error code. The `Info` field is a string that contains a human-readable error message.
The default `fuego.ErrorHandler` can be overridden using `fuego.WithErrorHandler` at fuego Server creation time.

If your error implements `Status() int` and `Info()` methods, the error will include the status code and the error message in the `fuego.HTTPError` response.
The error type of `fuego.HTTPError` is returned as JSON or XML depending on the content-type specified.
It's structure is the following:

```go
// HTTPError is the error response used by the serialization part of the framework.
type HTTPError struct {
// Developer readable error message. Not shown to the user to avoid security leaks.
Err error `json:"-" xml:"-"`
// URL of the error type. Can be used to lookup the error in a documentation
Type string `json:"type,omitempty" xml:"type,omitempty" description:"URL of the error type. Can be used to lookup the error in a documentation"`
// Short title of the error
Title string `json:"title,omitempty" xml:"title,omitempty" description:"Short title of the error"`
// HTTP status code. If using a different type than [HTTPError], for example [BadRequestError],
// this will be automatically overridden after Fuego error handling.
Status int `json:"status,omitempty" xml:"status,omitempty" description:"HTTP status code" example:"403"`
// Human readable error message
Detail string `json:"detail,omitempty" xml:"detail,omitempty" description:"Human readable error message"`
Instance string `json:"instance,omitempty" xml:"instance,omitempty"`
Errors []ErrorItem `json:"errors,omitempty" xml:"errors,omitempty"`
}
```

If your error implements `fuego.ErrorWithStatus` or `fuego.ErrorWithDetail`
the error will be returned as a `fuego.HTTPError`.

```go
// ErrorWithStatus is an interface that can be implemented by an error to provide
// a status code
type ErrorWithStatus interface {
error
StatusCode() int
}

// ErrorWithDetail is an interface that can be implemented by an error to provide
// an additional detail message about the error
type ErrorWithDetail interface {
error
DetailMsg() string
}
```

Example:

```go
type MyCustomError struct {
Status int
Message string
Err error `json:"error"`
Message string `json:"message"`
}

var _ fuego.ErrorWithStatus = MyCustomError{}
var _ 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]
}
```

func (e MyCustomError) Status() int {
return e.Status
Alternatively, you can always use `fuego.HTTPError` directly such as:

```go
err := fuego.HTTPError{
Title: "unauthorized access",
Detail: "wrong username or password",
Status: http.StatusUnauthorized,
}
```

Expand All @@ -33,3 +96,4 @@ Fuego provides a set of default errors that you can use in your application.
- `fuego.NotFoundError`: 404 Not Found
- `fuego.ConflictError`: 409 Conflict
- `fuego.InternalServerError`: 500 Internal Server Error
- `fuego.NotAcceptableError`: 406 Not Acceptable
19 changes: 13 additions & 6 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,31 @@ import (
)

// ErrorWithStatus is an interface that can be implemented by an error to provide
// additional information about the error.
// a status code
type ErrorWithStatus interface {
error
StatusCode() int
}

// ErrorWithDetail is an interface that can be implemented by an error to provide
// an additional detail message about the error
type ErrorWithDetail interface {
error
DetailMsg() string
}

// HTTPError is the error response used by the serialization part of the framework.
type HTTPError struct {
Err error `json:"-" xml:"-"` // Developer readable error message. Not shown to the user to avoid security leaks.
Type string `json:"type,omitempty" xml:"type,omitempty" description:"URL of the error type. Can be used to lookup the error in a documentation"` // URL of the error type. Can be used to lookup the error in a documentation
Title string `json:"title,omitempty" xml:"title,omitempty" description:"Short title of the error"` // Short title of the error
Status int `json:"status,omitempty" xml:"status,omitempty" description:"HTTP status code" example:"403"` // HTTP status code. If using a different type than [HTTPError], for example [BadRequestError], this will be automatically overridden after Fuego error handling.
Detail string `json:"detail,omitempty" xml:"detail,omitempty" description:"Human readable error message"` // Human readable error message
// Developer readable error message. Not shown to the user to avoid security leaks.
Err error `json:"-" xml:"-"`
// URL of the error type. Can be used to lookup the error in a documentation
Type string `json:"type,omitempty" xml:"type,omitempty" description:"URL of the error type. Can be used to lookup the error in a documentation"`
// Short title of the error
Title string `json:"title,omitempty" xml:"title,omitempty" description:"Short title of the error"`
// HTTP status code. If using a different type than [HTTPError], for example [BadRequestError], this will be automatically overridden after Fuego error handling.
Status int `json:"status,omitempty" xml:"status,omitempty" description:"HTTP status code" example:"403"`
// Human readable error message
Detail string `json:"detail,omitempty" xml:"detail,omitempty" description:"Human readable error message"`
Instance string `json:"instance,omitempty" xml:"instance,omitempty"`
Errors []ErrorItem `json:"errors,omitempty" xml:"errors,omitempty"`
}
Expand Down
1 change: 1 addition & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ func WithErrorSerializer(serializer ErrorSender) func(*Server) {
return func(c *Server) { c.SerializeError = serializer }
}

// WithErrorHandler sets a customer error handler for the server
func WithErrorHandler(errorHandler func(err error) error) func(*Server) {
return func(c *Server) { c.ErrorHandler = errorHandler }
}
Expand Down

0 comments on commit b39acd7

Please sign in to comment.