Errors as values
A handler โ and a middleware โ returns an error. One function decides what
that becomes on the wire, and a connection can never be left with no
response at all.
router.Handle(breeze.GET, "/orders/:id", func(ctx *breeze.Context) error {
order, err := store.Find(ctx.Param("id"))
if errors.Is(err, sql.ErrNoRows) {
return breeze.NewHTTPError(404, "no such order")
}
if err != nil {
// client sees "unavailable"; the driver's message goes to the log only
return breeze.WrapHTTPError(502, "the order service is unavailable", err)
}
return ctx.JSON(order)
}) What a returned error becomes
| Returned | Response |
|---|---|
*binding.ValidationError | 422 with field-level RFC 9457 detail |
*breeze.HTTPError | its status, with Message as the detail |
| anything else | logged to stderr, generic 500 |
Constructing an HTTPError
breeze.NewHTTPError(status int, message string) *HTTPError
breeze.WrapHTTPError(status int, message string, err error) *HTTPError WrapHTTPError is for the case where the client-facing message and the
underlying cause should not be the same string โ the wrapped error is
logged, message is what the client sees. HTTPError.Unwrap() returns the
wrapped error, so errors.Is / errors.As still work against it.
Why binding errors get their own status
A *binding.ValidationError becomes a 422, not a 400, because the
request was syntactically valid and semantically wrong โ the distinction a
client needs to decide whether to retry with different input or fix its
serialization. Malformed JSON gets the 400. See Request Binding & Validation for the full error
shape, which is RFC 9457 problem+json:
{
"type": "about:blank",
"status": 422,
"title": "Validation Failed",
"errors": [
{"field": "Name", "rule": "required", "message": "Name is required"},
{"field": "Email", "rule": "email", "message": "Email must be a valid email"}
]
} The fallback case
Anything that is neither a *binding.ValidationError nor a *breeze.HTTPError is logged to stderr with the request context, and the client gets a generic 500 with no internal detail leaked onto the wire. This is the safe
default: an unclassified error is assumed to carry information the caller
should not see.