Basic Examples¶
Drop-in patterns showing the core surface. Each example compiles against the current API.
Simple error¶
package main
import (
"fmt"
"github.com/hyp3rd/ewrap"
)
func main() {
err := ewrap.New("file not found")
fmt.Println(err.Error()) // file not found
fmt.Printf("%+v\n", err) // file not found + stack
}
Wrapping with context¶
import (
"context"
"net/http"
"github.com/hyp3rd/ewrap"
)
func loadUser(ctx context.Context, id string) (*User, error) {
u, err := db.Get(ctx, id)
if err != nil {
return nil, ewrap.Wrap(err, "loading user",
ewrap.WithContext(ctx, ewrap.ErrorTypeDatabase, ewrap.SeverityError),
ewrap.WithHTTPStatus(http.StatusInternalServerError)).
WithMetadata("user_id", id)
}
return u, nil
}
%w in Newf¶
import (
"errors"
"io"
"github.com/hyp3rd/ewrap"
)
func read() error {
err := ewrap.Newf("reading config: %w", io.EOF)
fmt.Println(err.Error()) // reading config: EOF
fmt.Println(errors.Is(err, io.EOF)) // true
return err
}
Validation accumulator¶
func validate(o Order) error {
eg := ewrap.NewErrorGroup()
if o.Customer == "" {
eg.Add(ewrap.New("customer is required",
ewrap.WithContext(nil, ewrap.ErrorTypeValidation, ewrap.SeverityError)).
WithMetadata("field", "customer"))
}
if o.Total <= 0 {
eg.Add(ewrap.New("total must be positive",
ewrap.WithContext(nil, ewrap.ErrorTypeValidation, ewrap.SeverityError)).
WithMetadata("field", "total"))
}
return eg.ErrorOrNil()
}
Pooled error group¶
var pool = ewrap.NewErrorGroupPool(8)
func process(items []Item) error {
eg := pool.Get()
defer eg.Release()
for _, it := range items {
eg.Add(handle(it))
}
return eg.Join()
}
Inspecting metadata¶
err := ewrap.New("payment failed").
WithMetadata("provider", "stripe").
WithMetadata("attempt", 2)
if v, ok := err.GetMetadata("provider"); ok {
fmt.Println(v) // stripe
}
if attempt, ok := ewrap.GetMetadataValue[int](err, "attempt"); ok {
fmt.Println(attempt) // 2
}
Walking the cause chain¶
import "errors"
err := ewrap.Wrap(io.EOF, "reading body")
errors.Is(err, io.EOF) // true
errors.Unwrap(err) // io.EOF
err.Cause() // io.EOF
Logging with slog directly¶
*Error implements slog.LogValuer, so you can pass it as a structured field with no adapter:
import (
"log/slog"
"os"
)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
err := ewrap.New("payment failed",
ewrap.WithContext(ctx, ewrap.ErrorTypeExternal, ewrap.SeverityError))
logger.Error("payment failed", "err", err)
// {"level":"ERROR","msg":"payment failed","err":{"message":"payment failed",
// "type":"external","severity":"error",...}}
Logging via (*Error).Log¶
For loggers attached as ewrap.Logger:
import ewrapslog "github.com/hyp3rd/ewrap/slog"
logger := ewrapslog.New(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
err := ewrap.New("payment failed",
ewrap.WithContext(ctx, ewrap.ErrorTypeExternal, ewrap.SeverityError),
ewrap.WithLogger(logger))
err.Log()
Pretty-printing for humans¶
fmt.Printf("%+v\n", err)
// payment failed
// /path/to/repo/billing.go:42 - example.com/repo/billing.charge
// /path/to/repo/handlers.go:71 - example.com/repo/handlers.Pay
// ...
Circuit breaker basics¶
import "github.com/hyp3rd/ewrap/breaker"
cb := breaker.New("payments", 3, time.Minute)
if !cb.CanExecute() {
return ewrap.New("payments breaker open",
ewrap.WithRetryable(true))
}
if err := charge(); err != nil {
cb.RecordFailure()
return ewrap.Wrap(err, "charging customer")
}
cb.RecordSuccess()
Classifying for the wire¶
func toResponse(w http.ResponseWriter, err error) {
status := ewrap.HTTPStatus(err)
if status == 0 {
status = http.StatusInternalServerError
}
msg := err.Error()
if e, ok := err.(*ewrap.Error); ok {
msg = e.SafeError() // PII-redacted variant
}
http.Error(w, msg, status)
}
Retry with classification¶
func withRetry(ctx context.Context, op func() error) error {
for attempt := 1; attempt <= 5; attempt++ {
err := op()
if err == nil {
return nil
}
if !ewrap.IsRetryable(err) {
return err
}
select {
case <-time.After(backoff(attempt)):
case <-ctx.Done():
return ewrap.Wrap(ctx.Err(), "retry budget exhausted")
}
}
return op()
}