|
| 1 | +--- |
| 2 | +name: goerr-error-handling |
| 3 | +description: Write Go error handling code using the goerr library. Use when creating errors, wrapping errors, adding context, or handling errors in Go code that uses goerr. |
| 4 | +allowed-tools: Read, Bash(go:*) |
| 5 | +--- |
| 6 | + |
| 7 | +# Error Handling with goerr |
| 8 | + |
| 9 | +When writing Go error handling code, use the `goerr` library for enhanced errors with stack traces, contextual values, and structured logging support. |
| 10 | + |
| 11 | +## Step 1: Locate goerr documentation |
| 12 | + |
| 13 | +First, find where goerr is installed and read its documentation: |
| 14 | + |
| 15 | +```bash |
| 16 | +GOERR_DIR=$(go list -m -f '{{.Dir}}' github.com/m-mizutani/goerr/v2 2>/dev/null) |
| 17 | +``` |
| 18 | + |
| 19 | +If goerr is available, read the relevant documentation from `$GOERR_DIR/`: |
| 20 | + |
| 21 | +- `README.md` - Full feature overview, code examples, and API reference |
| 22 | +- `docs/migration.md` - Migration guide from pkg/errors, standard errors, or goerr v1 |
| 23 | +- `examples/` - Working example programs for each feature |
| 24 | + |
| 25 | +## Step 2: Choose the right function |
| 26 | + |
| 27 | +| Situation | Use | NOT | |
| 28 | +|-----------|-----|-----| |
| 29 | +| Create a new error | `goerr.New("msg")` | `errors.New("msg")` or `fmt.Errorf("msg")` | |
| 30 | +| Wrap an existing error | `goerr.Wrap(err, "msg")` | `fmt.Errorf("msg: %w", err)` | |
| 31 | +| Add context without changing message | `goerr.With(err, goerr.V("k", v))` | `goerr.Wrap(err, err.Error(), goerr.V("k", v))` | |
| 32 | +| Simple key-value context | `goerr.V("key", value)` | Building context into message string | |
| 33 | +| Type-safe context | `goerr.TV(typedKey, value)` | `goerr.V()` when type safety matters | |
| 34 | +| Categorize errors | `goerr.T(tagValue)` | Sentinel errors for categories | |
| 35 | +| Shared context across errors | `goerr.NewBuilder(opts...)` | Repeating same options everywhere | |
| 36 | +| Collect multiple errors | `goerr.Append(errs, err)` | Manual slice management | |
| 37 | +| Define sentinel errors | `goerr.New("msg", goerr.ID("ERR_X"))` | `errors.New("msg")` | |
| 38 | +| Extract goerr.Error | `goerr.Unwrap(err)` | `errors.As(err, &goErr)` | |
| 39 | +| Extract goerr.Errors | `goerr.AsErrors(err)` | `errors.As(err, &goErrs)` | |
| 40 | +| Check error tag | `goerr.HasTag(err, tag)` | Manual unwrap + check | |
| 41 | + |
| 42 | +## Step 3: Apply best practices |
| 43 | + |
| 44 | +### Error messages should describe what failed, not why |
| 45 | + |
| 46 | +```go |
| 47 | +// Good |
| 48 | +goerr.Wrap(err, "failed to save user") |
| 49 | + |
| 50 | +// Bad - "why" belongs in context values |
| 51 | +goerr.Wrap(err, fmt.Sprintf("failed to save user %s due to timeout", userID)) |
| 52 | +``` |
| 53 | + |
| 54 | +### Use context values for structured data, not string interpolation |
| 55 | + |
| 56 | +```go |
| 57 | +// Good |
| 58 | +goerr.Wrap(err, "query failed", |
| 59 | + goerr.V("table", "users"), |
| 60 | + goerr.V("user_id", userID)) |
| 61 | + |
| 62 | +// Bad |
| 63 | +goerr.Wrap(err, fmt.Sprintf("query on table users failed for user %s", userID)) |
| 64 | +``` |
| 65 | + |
| 66 | +### Define typed keys at package level for critical values |
| 67 | + |
| 68 | +```go |
| 69 | +var ( |
| 70 | + UserIDKey = goerr.NewTypedKey[string]("user_id") |
| 71 | + CountKey = goerr.NewTypedKey[int]("count") |
| 72 | +) |
| 73 | + |
| 74 | +err := goerr.New("error", goerr.TV(UserIDKey, "user123")) |
| 75 | +if userID, ok := goerr.GetTypedValue(err, UserIDKey); ok { |
| 76 | + // userID is guaranteed to be string |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Define tags and sentinel errors at package level |
| 81 | + |
| 82 | +```go |
| 83 | +var ( |
| 84 | + TagNotFound = goerr.NewTag("not_found") |
| 85 | + TagValidation = goerr.NewTag("validation") |
| 86 | +) |
| 87 | + |
| 88 | +var ( |
| 89 | + ErrNotFound = goerr.New("not found", goerr.ID("ERR_NOT_FOUND")) |
| 90 | + ErrInvalid = goerr.New("invalid input", goerr.ID("ERR_INVALID")) |
| 91 | +) |
| 92 | +``` |
| 93 | + |
| 94 | +### Use Builder for shared context in a scope |
| 95 | + |
| 96 | +```go |
| 97 | +func (s *Service) process() error { |
| 98 | + eb := goerr.NewBuilder( |
| 99 | + goerr.V("user_id", s.userID), |
| 100 | + goerr.V("request_id", s.reqID)) |
| 101 | + |
| 102 | + if err := s.validate(); err != nil { |
| 103 | + return eb.Wrap(err, "validation failed") |
| 104 | + } |
| 105 | + if err := s.save(); err != nil { |
| 106 | + return eb.Wrap(err, "save failed") |
| 107 | + } |
| 108 | + return nil |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +### Use Unstack() for helper functions |
| 113 | + |
| 114 | +```go |
| 115 | +func newDomainError(msg string, opts ...goerr.Option) *goerr.Error { |
| 116 | + return goerr.New(msg, opts...).Unstack() |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +### Use slog integration for structured logging |
| 121 | + |
| 122 | +```go |
| 123 | +logger.Error("operation failed", slog.Any("error", err)) |
| 124 | +// Automatically includes message, values, tags, and stack trace |
| 125 | +``` |
| 126 | + |
| 127 | +## Quick Reference |
| 128 | + |
| 129 | +**Error creation**: `goerr.New`, `goerr.Wrap`, `goerr.With` |
| 130 | + |
| 131 | +**Options**: `goerr.Value` / `goerr.V`, `goerr.TypedValue` / `goerr.TV`, `goerr.Tag` / `goerr.T`, `goerr.ID` |
| 132 | + |
| 133 | +**Extraction**: `goerr.Unwrap`, `goerr.AsErrors`, `goerr.Values`, `goerr.TypedValues`, `goerr.Tags`, `goerr.HasTag`, `goerr.GetTypedValue` |
| 134 | + |
| 135 | +**Multiple errors**: `goerr.Join`, `goerr.Append`, `(*Errors).ErrorOrNil`, `(*Errors).IsEmpty`, `(*Errors).Len` |
| 136 | + |
| 137 | +**Builder**: `goerr.NewBuilder`, `(*Builder).With`, `(*Builder).New`, `(*Builder).Wrap` |
| 138 | + |
| 139 | +**Type-safe keys**: `goerr.NewTypedKey[T]`, `goerr.TV`, `goerr.GetTypedValue` |
| 140 | + |
| 141 | +**Tags**: `goerr.NewTag`, `goerr.T`, `goerr.HasTag`, `(*Error).HasTag` |
| 142 | + |
| 143 | +**Stack control**: `(*Error).Unstack`, `(*Error).UnstackN` |
0 commit comments