Go cookbook · Beginner
Wrap, inspect and expose errors properly
Errors lose context as they bubble up, and handlers can't tell 'not found' from a database outage.
Approach
Define sentinel errors for expected conditions and error types for ones that carry data. Wrap with %w to add
context, inspect with errors.Is/errors.As at the boundary, and map to HTTP status codes there.
Solution
package orders
import (
"errors"
"fmt"
"net/http"
)
var ErrNotFound = errors.New("not found")
type ValidationError struct {
Field, Message string
}
func (e *ValidationError) Error() string { return e.Field + ": " + e.Message }
type Order struct {
ID string
Quantity int
}
func Load(id string) (Order, error) {
if id != "o1" {
return Order{}, fmt.Errorf("load order %q: %w", id, ErrNotFound)
}
return Order{ID: id, Quantity: 1}, nil
}
func UpdateQuantity(id string, qty int) error {
if qty < 1 {
return &ValidationError{Field: "quantity", Message: "must be at least 1"}
}
if _, err := Load(id); err != nil {
return fmt.Errorf("update quantity: %w", err)
}
return nil
}
func StatusFor(err error) (int, string) {
var verr *ValidationError
switch {
case err == nil:
return http.StatusOK, ""
case errors.Is(err, ErrNotFound):
return http.StatusNotFound, "order not found"
case errors.As(err, &verr):
return http.StatusUnprocessableEntity, verr.Error()
default:
return http.StatusInternalServerError, "internal error" // log err, don't expose it
}
}
Pitfalls
- Comparing errors with
==after wrapping; useerrors.Is. - Wrapping with
%v, which breaks the chain. - Logging an error at every level and returning it, producing duplicate logs; log once at the boundary.
Variations
errors.Joincombines several errors (for example from cleanup) into one.- Return
(T, bool)instead of an error when absence is a normal outcome.
Further reading: errors package
Reference: https://pkg.go.dev/errors
More Go recipes
- Benchmark code with testing.B.Loop
- Build a small, secure container image for a Go service
- Decode and validate JSON request bodies safely
- Embed static files and templates in the binary
- Load and validate configuration from environment variables
- Log structured JSON with slog and redact secrets
- Process jobs concurrently with a bounded worker pool
- Profile a running service with pprof