Skip to content

Instantly share code, notes, and snippets.

@brandonhimpfen
Created January 17, 2026 00:25
Show Gist options
  • Select an option

  • Save brandonhimpfen/213d21e83bed6b92d49670e45d2d6ffb to your computer and use it in GitHub Desktop.

Select an option

Save brandonhimpfen/213d21e83bed6b92d49670e45d2d6ffb to your computer and use it in GitHub Desktop.
Go custom error wrapping pattern using %w, errors.Is/As, and optional typed sentinel errors (clean, idiomatic, and debuggable).
package main
import (
"errors"
"fmt"
)
// Sentinel errors (useful for high-level classification)
var (
ErrNotFound = errors.New("not found")
ErrInvalidInput = errors.New("invalid input")
)
// Custom typed error that wraps an underlying cause.
// This lets callers use errors.As() to extract structured info.
type AppError struct {
Op string // operation name (e.g., "db.GetUser")
Code error // optional sentinel category (e.g., ErrNotFound)
Err error // underlying error (wrapped)
}
func (e *AppError) Error() string {
// Keep it readable for logs
if e.Code != nil {
return fmt.Sprintf("%s: %v: %v", e.Op, e.Code, e.Err)
}
return fmt.Sprintf("%s: %v", e.Op, e.Err)
}
// Unwrap enables errors.Is / errors.As to traverse the chain.
func (e *AppError) Unwrap() error { return e.Err }
// Is lets errors.Is(err, ErrNotFound) match when AppError.Code == ErrNotFound.
// This is optional but convenient when you want a "category" error.
func (e *AppError) Is(target error) bool {
return e.Code != nil && target == e.Code
}
// Helper constructor to standardize creation and wrapping.
func Wrap(op string, code error, err error) error {
if err == nil {
return nil
}
return &AppError{Op: op, Code: code, Err: err}
}
// ----------------------------------------------------------------------
// Example usage
// ----------------------------------------------------------------------
func getUser(id string) (string, error) {
const op = "service.getUser"
if id == "" {
return "", Wrap(op, ErrInvalidInput, fmt.Errorf("id is empty"))
}
// Simulate a lower-level error (e.g., from db driver)
dbErr := fmt.Errorf("query users where id=%q: %w", id, ErrNotFound)
// Wrap it with operation + category.
return "", Wrap(op, ErrNotFound, dbErr)
}
func main() {
_, err := getUser("123")
if err != nil {
fmt.Println("error:", err)
// Classification via sentinel errors
if errors.Is(err, ErrNotFound) {
fmt.Println("-> handle 404 / not found")
}
// Extract structured info
var appErr *AppError
if errors.As(err, &appErr) {
fmt.Println("-> operation:", appErr.Op)
if appErr.Code != nil {
fmt.Println("-> category:", appErr.Code)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment