srs/internal/errors/errors.go
winlin f7f0676c9e Proxy: Rewrite internal/errors on stdlib with a single withStack struct.
Replaces the pkg/errors fork with a thin wrapper over the standard
library's errors package. A single withStack struct captures stack
traces via runtime.Callers, while fmt.Errorf("%w", ...) handles all
message wrapping. This enables errors.Is/As/Unwrap chain traversal
(the fork silently broke them) and deletes ~190 lines of stack/frame
formatting code. Adds table-driven tests at 100% statement coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:36:59 -04:00

154 lines
3.5 KiB
Go

// Package errors provides error handling primitives with stack traces.
//
// It is a thin layer over the standard library's errors package, adding a
// stack trace at the point an error is created or wrapped. The wrapping
// chain is fully compatible with errors.Is, errors.As, and errors.Unwrap.
//
// # Adding context to an error
//
// _, err := io.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// # Formatted printing of errors
//
// %s the error message (full wrap chain)
// %v same as %s
// %+v the error message followed by the captured stack trace
// %q the error message, quoted
//
// # Retrieving the stack trace
//
// Errors returned by this package satisfy the following interface:
//
// type stackTracer interface {
// StackTrace() []uintptr
// }
package errors
import (
"errors"
"fmt"
"runtime"
)
// Re-exported stdlib primitives so callers can use a single import.
var (
Is = errors.Is
As = errors.As
Unwrap = errors.Unwrap
Join = errors.Join
)
// withStack wraps an error with a captured stack trace.
type withStack struct {
err error
pcs []uintptr
}
func (e *withStack) Error() string {
return e.err.Error()
}
func (e *withStack) Unwrap() error {
return e.err
}
func (e *withStack) StackTrace() []uintptr {
return e.pcs
}
func (e *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprint(s, e.err.Error())
frames := runtime.CallersFrames(e.pcs)
for {
f, more := frames.Next()
fmt.Fprintf(s, "\n%s\n\t%s:%d", f.Function, f.File, f.Line)
if !more {
break
}
}
return
}
fallthrough
case 's':
fmt.Fprint(s, e.err.Error())
case 'q':
fmt.Fprintf(s, "%q", e.err.Error())
}
}
func callers() []uintptr {
var pcs [32]uintptr
n := runtime.Callers(3, pcs[:])
return pcs[:n]
}
func attach(err error) error {
return &withStack{err: err, pcs: callers()}
}
// New returns an error with the supplied message and a captured stack trace.
func New(message string) error {
return attach(errors.New(message))
}
// Errorf formats according to a format specifier and returns a new error with
// a captured stack trace. It supports %w for wrapping an existing error.
func Errorf(format string, args ...any) error {
return attach(fmt.Errorf(format, args...))
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return attach(err)
}
// WithMessage annotates err with a new message, without capturing a stack.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", message, err)
}
// Wrap returns an error annotating err with a message and a captured stack.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
return attach(fmt.Errorf("%s: %w", message, err))
}
// Wrapf is the formatting variant of Wrap.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...any) error {
if err == nil {
return nil
}
return attach(fmt.Errorf(fmt.Sprintf(format, args...)+": %w", err))
}
// Cause walks the error's Unwrap chain and returns the root error.
// New code should prefer errors.Is or errors.As.
func Cause(err error) error {
for err != nil {
u := errors.Unwrap(err)
if u == nil {
return err
}
err = u
}
return nil
}