snippetgoModeratepending
Go error wrapping and unwrapping
Viewed 0 times
error-wrappingfmt.Errorferrors.Iserrors.Assentinelcontext
Problem
Go errors lose context as they propagate up the call stack. Need to add context while preserving the original error for type checking.
Solution
Use fmt.Errorf with %w for error wrapping:
import (
"errors"
"fmt"
)
// Define sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrPermission = errors.New("permission denied")
)
// Wrap errors with context
func getUser(id string) (*User, error) {
user, err := db.Query(id)
if err != nil {
return nil, fmt.Errorf("getUser(%s): %w", id, err)
}
if user == nil {
return nil, fmt.Errorf("getUser(%s): %w", id, ErrNotFound)
}
return user, nil
}
// Check wrapped errors
func handler(w http.ResponseWriter, r *http.Request) {
user, err := getUser(r.URL.Query().Get("id"))
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", 404)
return
}
http.Error(w, "Internal error", 500)
return
}
}
// errors.Is checks through the entire wrap chain
// errors.As extracts a specific error type:
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Path:", pathErr.Path)
}
// Multi-error (Go 1.20+):
err := errors.Join(err1, err2, err3)
errors.Is(err, err1) // true
errors.Is(err, err2) // true
import (
"errors"
"fmt"
)
// Define sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrPermission = errors.New("permission denied")
)
// Wrap errors with context
func getUser(id string) (*User, error) {
user, err := db.Query(id)
if err != nil {
return nil, fmt.Errorf("getUser(%s): %w", id, err)
}
if user == nil {
return nil, fmt.Errorf("getUser(%s): %w", id, ErrNotFound)
}
return user, nil
}
// Check wrapped errors
func handler(w http.ResponseWriter, r *http.Request) {
user, err := getUser(r.URL.Query().Get("id"))
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "User not found", 404)
return
}
http.Error(w, "Internal error", 500)
return
}
}
// errors.Is checks through the entire wrap chain
// errors.As extracts a specific error type:
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Path:", pathErr.Path)
}
// Multi-error (Go 1.20+):
err := errors.Join(err1, err2, err3)
errors.Is(err, err1) // true
errors.Is(err, err2) // true
Why
Error wrapping with %w adds context for debugging while preserving the ability to check for specific errors with errors.Is/As.
Revisions (0)
No revisions yet.