Skip to content

Commit be2f9b2

Browse files
authored
docs(goerr-error-handling): add comprehensive guide for error handling using the goerr library to enhance Go error management practices (#34)
1 parent 95f301f commit be2f9b2

File tree

1 file changed

+143
-0
lines changed
  • docs/skills/goerr-error-handling

1 file changed

+143
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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

Comments
 (0)