An error chain is a linked sequence of wrapped Go error values. Wrapping preserves an underlying cause while adding context such as “validating,” “saving,” or “retrieving.” section 4.8 section 8.2.3
Use error chains to preserve context without losing errors that callers need to identify:
- Wrap causes with fmt.Errorf and the %w verb, for example fmt.Errorf("unexpected issue: %w", ErrBadRequest). section 4.8
- Use errors.Is rather than direct equality when checking for a sentinel error inside a chain. section 8.2.3
- Use errors.As to extract a particular error type from the chain, then inspect its fields or codes. section 10.2.4
- Return application-level errors after recognizing internal driver errors, preventing sensitive implementation details from leaking to callers. section 10.2.4
Classifying a wrapped service error
A service can add context while retaining a recognizable cause: err := fmt.Errorf("unexpected issue: %w", ErrBadRequest); if errors.Is(err, ErrBadRequest) { /* handle the bad request */ }. Comparing err == ErrBadRequest returns false because err is a chain rather than the original value. section 8.2.3
Listing 8.5 Shortener service (link/shortener.go)
package link
import (
"context"
"fmt"
)
// Shortener shortens and resolves [Link] values from an in-memory storage.
type Shortener struct {
links map[Key]Link #1
}
// Shorten shortens the [Link] URL and may update the [Key] if the key
// is empty. If the link is valid, it stores it in an in-memory storage.
func (s *Shortener) Shorten(
_ context.Context, lnk Link, #2
) (Key, error) {
var err error
if lnk.Key, err = Shorten(lnk); err != nil { #3
return "", fmt.Errorf("%w: %w", err, ErrBadRequest)
}
// Persist the link in the in-memory storage.
if _, ok := s.links[lnk.Key]; ok { #4
return "", fmt.Errorf( #4
"saving: %w", ErrConflict, #4
) #4
} #4
if s.links == nil { #5
s.links = map[Key]Link{} #5
} #5
s.links[lnk.Key] = lnk #6
return lnk.Key, nil
}
Inspect a complete Shortener service implementation that wraps validation and storage failures with contextual errors and application-level causes. section 8.2.3
Listing 10.8 Detecting constraint errors (link/sqlite/sqlite.go)
package sqlite
import (
// _ "modernc.org/sqlite" #1
. . .
"errors"
"modernc.org/sqlite" #2
)
. . .
func isPrimaryKeyViolation(err error) bool {
var serr *sqlite.Error #3
if errors.As(err, &serr) { #4
return serr.Code() == 1555 #5
// See: sqlite.org/rescode.html#constraint_primarykey
}
return false
}
Inspect typed chain matching with errors.As, including extraction of a SQLite driver error and checking its constraint code. section 10.2.4
