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.

Create a free account to save this page, take notes and track your progress. Sign in

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; use errors.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.Join combines 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

Was this page helpful?