liveBook · Manning

Error chain

As explained in Go by Example

An error chain is a linked sequence of wrapped Go error values that preserves an underlying cause and added context.

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

How Go by Example covers it

How deep it goes
Explains %w wrapping, errors.Is classification, and errors.As extraction, including pointer requirements for typed errors.
Examples
Uses CLI failures, Shortener validation and storage errors, and SQLite primary-key violations as concrete chain examples.
What it assumes
Assumes Go error values and interfaces, while demonstrating chain handling through standard-library fmt and errors functions.
What you can do after
Diagnose/debug with errors.Is and errors.As, then return informative application-level errors instead of leaking internal causes.
How it connects
Connects fmt.Errorf, errors.Is, errors.As, sentinel errors, typed errors, and the error interface.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go by Example. The section links point to the parts of the book it draws on.