diff --git a/README.md b/README.md index 92e6f38..e6dc8fc 100644 --- a/README.md +++ b/README.md @@ -13,28 +13,45 @@ import "github.com/go-coldbrew/errors" ``` -Package errors provides an implementation of golang error with stack strace information attached to it, the error objects created by this package are compatible with https://golang.org/pkg/errors/ +Package errors is a drop\-in replacement for the standard library "errors" package that adds stack trace capture, gRPC status codes, and error notification support. -How To Use The simplest way to use this package is by calling one of the two functions +The following standard library helpers are re\-exported: [Is](<#Is>), [As](<#As>), [Unwrap](<#Unwrap>), [Join](<#Join>), and the [ErrUnsupported](<#ErrUnsupported>) sentinel. This allows you to use this package as your sole errors import: + +``` +import "github.com/go-coldbrew/errors" + +// Standard library functions work as expected: +errors.Is(err, target) +errors.As(err, &target) +errors.Unwrap(err) +errors.Join(err1, err2) + +// ColdBrew extensions add stack traces and gRPC status: +errors.New("something failed") // captures stack trace +errors.Wrap(err, "context") // wraps with stack trace +errors.Cause(err) // walks Unwrap chain to root cause +``` + +### Error Creation + +The simplest way to use this package is by calling one of the two functions: ``` errors.New(...) errors.Wrap(...) ``` -You can also initialize custom error stack by using one of the \`WithSkip\` functions. \`WithSkip\` allows skipping the defined number of functions from the stack information. +You can also initialize custom error stack by using one of the WithSkip functions. WithSkip allows skipping the defined number of functions from the stack information. ``` -if you want to create a new error use New -if you want to skip some functions on the stack use NewWithSkip -if you want to add GRPC status use NewWithStatus -if you want to skip some functions on the stack and add GRPC status use NewWithSkipAndStatus -if you want to wrap an existing error use Wrap -if you want to wrap an existing error and add GRPC status use WrapWithStatus -if you want to wrap an existing error and skip some functions on the stack use WrapWithSkip -if you want to wrap an existing error, skip some functions on the stack and add GRPC status use WrapWithSkipAndStatus -if you want to wrap an existing error and add notifier options use WrapWithNotifier -if you want to wrap an existing error, skip some functions on the stack and add notifier options use WrapWithSkipAndNotifier +New — create a new error with stack info +NewWithSkip — skip functions on the stack +NewWithStatus — add GRPC status +NewWithSkipAndStatus — skip functions and add GRPC status +Wrap — wrap an existing error +WrapWithStatus — wrap and add GRPC status +WrapWithSkip — wrap and skip functions on the stack +WrapWithSkipAndStatus — wrap, skip functions, and add GRPC status ``` Head to https://docs.coldbrew.cloud for more information. @@ -108,8 +125,15 @@ true ## Index - [Constants](<#constants>) +- [Variables](<#variables>) +- [func As\(err error, target any\) bool](<#As>) +- [func AsType\[E error\]\(err error\) \(E, bool\)](<#AsType>) +- [func Cause\(err error\) error](<#Cause>) +- [func Is\(err, target error\) bool](<#Is>) +- [func Join\(errs ...error\) error](<#Join>) - [func SetBaseFilePath\(path string\)](<#SetBaseFilePath>) - [func SetMaxStackDepth\(n int\)](<#SetMaxStackDepth>) +- [func Unwrap\(err error\) error](<#Unwrap>) - [type ErrorExt](<#ErrorExt>) - [func New\(msg string\) ErrorExt](<#New>) - [func NewWithSkip\(msg string, skip int\) ErrorExt](<#NewWithSkip>) @@ -133,6 +157,206 @@ true const SupportPackageIsVersion1 = true ``` +## Variables + +ErrUnsupported indicates that a requested operation cannot be performed, because it is unsupported. + +Re\-exported from the standard library errors package. + +```go +var ErrUnsupported = stderrors.ErrUnsupported +``` + + +## func [As]() + +```go +func As(err error, target any) bool +``` + +As finds the first error in err's tree that matches target, and if one is found, sets target to that error value and returns true. + +Re\-exported from the standard library errors package. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + "github.com/go-coldbrew/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func main() { + grpcErr := errors.NewWithStatus("not found", status.New(codes.NotFound, "not found")) + wrapped := errors.Wrap(grpcErr, "lookup failed") + + var ext errors.ErrorExt + if errors.As(wrapped, &ext) { + fmt.Println("found ErrorExt:", ext.GRPCStatus().Code()) + } +} +``` + +#### Output + +``` +found ErrorExt: NotFound +``` + +

+
+ + +## func [AsType]() + +```go +func AsType[E error](err error) (E, bool) +``` + +AsType finds the first error in err's tree that matches the type E, and if one is found, returns that error value and true. Otherwise, it returns the zero value of E and false. + +Re\-exported from the standard library errors package \(requires Go 1.26\+\). + + +## func [Cause]() + +```go +func Cause(err error) error +``` + +Cause walks the [Unwrap](<#Unwrap>) chain of err and returns the innermost \(root cause\) error. If err does not implement Unwrap, err itself is returned. If err is nil, nil is returned. + +For [ErrorExt](<#ErrorExt>) errors, this produces the same result as calling the Cause method, but this function works on any error that implements the standard Unwrap interface. + +Note: for multi\-errors \(errors implementing Unwrap\(\) \[\]error, such as those created by [Join](<#Join>)\), the single\-error Unwrap returns nil, so Cause returns the multi\-error itself. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + "io" + + "github.com/go-coldbrew/errors" +) + +func main() { + root := io.EOF + first := errors.Wrap(root, "read body") + second := errors.Wrap(first, "handle request") + fmt.Println(errors.Cause(second)) +} +``` + +#### Output + +``` +EOF +``` + +

+
+ + +## func [Is]() + +```go +func Is(err, target error) bool +``` + +Is reports whether any error in err's tree matches target. + +An error is considered a match if it is equal to the target or if it implements an Is\(error\) bool method such that Is\(target\) returns true. + +Re\-exported from the standard library errors package. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + "github.com/go-coldbrew/errors" +) + +func main() { + base := fmt.Errorf("connection refused") + wrapped := errors.Wrap(base, "dial failed") + fmt.Println(errors.Is(wrapped, base)) +} +``` + +#### Output + +``` +true +``` + +

+
+ + +## func [Join]() + +```go +func Join(errs ...error) error +``` + +Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if every value in errs is nil. + +Re\-exported from the standard library errors package. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + "github.com/go-coldbrew/errors" +) + +func main() { + err1 := errors.New("first") + err2 := errors.New("second") + joined := errors.Join(err1, err2) + fmt.Println(errors.Is(joined, err1)) + fmt.Println(errors.Is(joined, err2)) +} +``` + +#### Output + +``` +true +true +``` + +

+
+ ## func [SetBaseFilePath]() @@ -151,6 +375,48 @@ func SetMaxStackDepth(n int) SetMaxStackDepth sets the maximum number of stack frames captured when creating errors. Accepts values in \[1, 256\]; out\-of\-range values are ignored. Default is 16. Safe for concurrent use. + +## func [Unwrap]() + +```go +func Unwrap(err error) error +``` + +Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil. + +Re\-exported from the standard library errors package. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + "io" + + "github.com/go-coldbrew/errors" +) + +func main() { + base := io.EOF + wrapped := errors.Wrap(base, "read failed") + fmt.Println(errors.Unwrap(wrapped)) +} +``` + +#### Output + +``` +EOF +``` + +

+
+ ## type [ErrorExt]() @@ -319,13 +585,12 @@ cause: EOF
Example (Errors Is)

-Wrapped errors are compatible with stdlib errors.Is for unwrapping. +Wrapped errors are compatible with errors.Is for unwrapping. No separate "errors" import needed — Is is re\-exported. ```go package main import ( - stderrors "errors" "fmt" "io" @@ -335,7 +600,7 @@ import ( func main() { original := io.EOF wrapped := errors.Wrap(original, "read failed") - fmt.Println(stderrors.Is(wrapped, io.EOF)) + fmt.Println(errors.Is(wrapped, io.EOF)) } ``` diff --git a/doc.go b/doc.go index 378a648..c15668f 100644 --- a/doc.go +++ b/doc.go @@ -1,26 +1,42 @@ /* -Package errors provides an implementation of golang error with stack strace information attached to it, -the error objects created by this package are compatible with https://golang.org/pkg/errors/ +Package errors is a drop-in replacement for the standard library "errors" package +that adds stack trace capture, gRPC status codes, and error notification support. -How To Use -The simplest way to use this package is by calling one of the two functions +The following standard library helpers are re-exported: +[Is], [As], [Unwrap], [Join], and the [ErrUnsupported] sentinel. +This allows you to use this package as your sole errors import: + + import "github.com/go-coldbrew/errors" + + // Standard library functions work as expected: + errors.Is(err, target) + errors.As(err, &target) + errors.Unwrap(err) + errors.Join(err1, err2) + + // ColdBrew extensions add stack traces and gRPC status: + errors.New("something failed") // captures stack trace + errors.Wrap(err, "context") // wraps with stack trace + errors.Cause(err) // walks Unwrap chain to root cause + +# Error Creation + +The simplest way to use this package is by calling one of the two functions: errors.New(...) errors.Wrap(...) -You can also initialize custom error stack by using one of the `WithSkip` functions. `WithSkip` allows +You can also initialize custom error stack by using one of the WithSkip functions. WithSkip allows skipping the defined number of functions from the stack information. - if you want to create a new error use New - if you want to skip some functions on the stack use NewWithSkip - if you want to add GRPC status use NewWithStatus - if you want to skip some functions on the stack and add GRPC status use NewWithSkipAndStatus - if you want to wrap an existing error use Wrap - if you want to wrap an existing error and add GRPC status use WrapWithStatus - if you want to wrap an existing error and skip some functions on the stack use WrapWithSkip - if you want to wrap an existing error, skip some functions on the stack and add GRPC status use WrapWithSkipAndStatus - if you want to wrap an existing error and add notifier options use WrapWithNotifier - if you want to wrap an existing error, skip some functions on the stack and add notifier options use WrapWithSkipAndNotifier + New — create a new error with stack info + NewWithSkip — skip functions on the stack + NewWithStatus — add GRPC status + NewWithSkipAndStatus — skip functions and add GRPC status + Wrap — wrap an existing error + WrapWithStatus — wrap and add GRPC status + WrapWithSkip — wrap and skip functions on the stack + WrapWithSkipAndStatus — wrap, skip functions, and add GRPC status Head to https://docs.coldbrew.cloud for more information. */ diff --git a/errors_test.go b/errors_test.go index f38e8c2..0ccca95 100644 --- a/errors_test.go +++ b/errors_test.go @@ -9,7 +9,7 @@ import ( ) func TestWrap(t *testing.T) { - var tests = []struct { + tests := []struct { name string err error message string @@ -39,7 +39,6 @@ func TestWrap(t *testing.T) { if grpcstatus.Convert(err).Message() != tt.expected { t.Errorf("GRPC status msg expected %+v, got %+v", tt.expected, grpcstatus.Convert(err).Message()) } - }) } } @@ -51,22 +50,22 @@ func TestErrorsIs(t *testing.T) { wrapped2 := Wrap(wrapped1, "layer2") wrapped3 := Wrap(wrapped2, "layer3") - if !stderrors.Is(wrapped1, base) { + if !Is(wrapped1, base) { t.Error("wrapped1 should match base via errors.Is") } - if !stderrors.Is(wrapped2, wrapped1) { + if !Is(wrapped2, wrapped1) { t.Error("wrapped2 should match wrapped1 via errors.Is") } - if !stderrors.Is(wrapped2, base) { + if !Is(wrapped2, base) { t.Error("wrapped2 should match base via errors.Is") } - if !stderrors.Is(wrapped3, wrapped2) { + if !Is(wrapped3, wrapped2) { t.Error("wrapped3 should match wrapped2 via errors.Is") } - if !stderrors.Is(wrapped3, wrapped1) { + if !Is(wrapped3, wrapped1) { t.Error("wrapped3 should match wrapped1 via errors.Is") } - if !stderrors.Is(wrapped3, base) { + if !Is(wrapped3, base) { t.Error("wrapped3 should match base via errors.Is") } } @@ -99,7 +98,7 @@ func TestWrapf(t *testing.T) { if err.Error() != expected { t.Errorf("Wrapf() = %q, want %q", err.Error(), expected) } - if !stderrors.Is(err, base) { + if !Is(err, base) { t.Error("Wrapf result should match base via errors.Is") } } @@ -169,6 +168,113 @@ func TestSetMaxStackDepth(t *testing.T) { } } +func TestIs(t *testing.T) { + base := stderrors.New("base") + wrapped := Wrap(base, "wrapped") + + if !Is(wrapped, base) { + t.Error("Is should find base through ColdBrew wrap") + } + if Is(wrapped, stderrors.New("other")) { + t.Error("Is should not match unrelated error") + } + if !Is(wrapped, wrapped) { + t.Error("Is should match error against itself") + } +} + +func TestAs(t *testing.T) { + grpcErr := NewWithStatus("not found", grpcstatus.New(5, "not found")) + wrapped := Wrap(grpcErr, "lookup failed") + + var ext ErrorExt + if !As(wrapped, &ext) { + t.Fatal("As should find ErrorExt in chain") + } + if ext.Error() != "lookup failed: not found" { + t.Errorf("As target = %q, want %q", ext.Error(), "lookup failed: not found") + } +} + +func TestUnwrap(t *testing.T) { + base := stderrors.New("base") + wrapped := Wrap(base, "ctx") + + unwrapped := Unwrap(wrapped) + if unwrapped == nil { + t.Fatal("Unwrap should return non-nil for wrapped error") + } + if unwrapped != base { + t.Errorf("Unwrap = %v, want %v", unwrapped, base) + } + + // Unwrap on a non-wrapping error returns nil + plain := stderrors.New("plain") + if Unwrap(plain) != nil { + t.Error("Unwrap on non-wrapping error should return nil") + } +} + +func TestJoin(t *testing.T) { + err1 := New("first") + err2 := stderrors.New("second") + + joined := Join(err1, err2) + if joined == nil { + t.Fatal("Join should return non-nil") + } + if !Is(joined, err1) { + t.Error("Join result should contain first error") + } + if !Is(joined, err2) { + t.Error("Join result should contain second error") + } + + // All nils returns nil + if Join(nil, nil) != nil { + t.Error("Join of all nils should return nil") + } +} + +func TestErrUnsupported(t *testing.T) { + err := stderrors.ErrUnsupported + if !Is(err, ErrUnsupported) { + t.Error("ErrUnsupported should match stdlib ErrUnsupported via Is") + } +} + +func TestCauseStandalone(t *testing.T) { + // Works on ColdBrew error chain + root := stderrors.New("root") + a := Wrap(root, "a") + b := Wrap(a, "b") + + if got := Cause(b); got != root { + t.Errorf("Cause(b) = %v, want %v", got, root) + } + if got := Cause(a); got != root { + t.Errorf("Cause(a) = %v, want %v", got, root) + } + + // Works on plain stdlib errors (no Unwrap) + plain := stderrors.New("plain") + if got := Cause(plain); got != plain { + t.Errorf("Cause(plain) = %v, want %v", got, plain) + } + + // Returns nil for nil + if got := Cause(nil); got != nil { + t.Errorf("Cause(nil) = %v, want nil", got) + } + + // Join multi-errors use Unwrap() []error, not Unwrap() error, + // so single-error Unwrap returns nil and Cause returns the Join itself. + joined := stderrors.Join(io.EOF) + if got := Cause(joined); got != joined { + t.Errorf("Cause(joinedErr) = %v, want %v", got, joined) + } +} + func TestStackFrameConsistency(t *testing.T) { err := New("consistency test") @@ -196,4 +302,3 @@ func TestStackFrameConsistency(t *testing.T) { t.Fatalf("StackFrame count %d exceeds Callers count %d", len(frames1), len(pcs)) } } - diff --git a/example_test.go b/example_test.go index 2593ea6..0366254 100644 --- a/example_test.go +++ b/example_test.go @@ -1,7 +1,6 @@ package errors_test import ( - stderrors "errors" "fmt" "io" @@ -73,10 +72,55 @@ func Example_cause() { // cause: EOF } -// Wrapped errors are compatible with stdlib errors.Is for unwrapping. +// Wrapped errors are compatible with errors.Is for unwrapping. +// No separate "errors" import needed — Is is re-exported. func ExampleWrap_errorsIs() { original := io.EOF wrapped := errors.Wrap(original, "read failed") - fmt.Println(stderrors.Is(wrapped, io.EOF)) + fmt.Println(errors.Is(wrapped, io.EOF)) // Output: true } + +func ExampleIs() { + base := fmt.Errorf("connection refused") + wrapped := errors.Wrap(base, "dial failed") + fmt.Println(errors.Is(wrapped, base)) + // Output: true +} + +func ExampleAs() { + grpcErr := errors.NewWithStatus("not found", status.New(codes.NotFound, "not found")) + wrapped := errors.Wrap(grpcErr, "lookup failed") + + var ext errors.ErrorExt + if errors.As(wrapped, &ext) { + fmt.Println("found ErrorExt:", ext.GRPCStatus().Code()) + } + // Output: found ErrorExt: NotFound +} + +func ExampleJoin() { + err1 := errors.New("first") + err2 := errors.New("second") + joined := errors.Join(err1, err2) + fmt.Println(errors.Is(joined, err1)) + fmt.Println(errors.Is(joined, err2)) + // Output: + // true + // true +} + +func ExampleUnwrap() { + base := io.EOF + wrapped := errors.Wrap(base, "read failed") + fmt.Println(errors.Unwrap(wrapped)) + // Output: EOF +} + +func ExampleCause() { + root := io.EOF + first := errors.Wrap(root, "read body") + second := errors.Wrap(first, "handle request") + fmt.Println(errors.Cause(second)) + // Output: EOF +} diff --git a/notifier/README.md b/notifier/README.md index 1414300..b3f511f 100755 --- a/notifier/README.md +++ b/notifier/README.md @@ -41,7 +41,7 @@ import "github.com/go-coldbrew/errors/notifier" -## func [Close]() +## func [Close]() ```go func Close() @@ -59,7 +59,7 @@ func GetTraceHeaderName() string GetTraceHeaderName gets the header name for trace id default is x\-trace\-id -## func [GetTraceId]() +## func [GetTraceId]() ```go func GetTraceId(ctx context.Context) string @@ -86,7 +86,7 @@ func InitRollbar(token, env string) InitRollbar inits rollbar configuration token: rollbar token env: rollbar environment -## func [InitSentry]() +## func [InitSentry]() ```go func InitSentry(dsn string) @@ -95,7 +95,7 @@ func InitSentry(dsn string) InitSentry inits sentry configuration dsn: sentry dsn -## func [Notify]() +## func [Notify]() ```go func Notify(err error, rawData ...interface{}) error @@ -113,7 +113,7 @@ func NotifyAsync(err error, rawData ...interface{}) error NotifyAsync sends an error notification asynchronously with bounded concurrency. If the async notification pool is full, the notification is dropped to prevent goroutine explosion under sustained error bursts. Returns the original error for convenience. -## func [NotifyOnPanic]() +## func [NotifyOnPanic]() ```go func NotifyOnPanic(rawData ...interface{}) @@ -122,7 +122,7 @@ func NotifyOnPanic(rawData ...interface{}) NotifyOnPanic notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata this function should be called in defer example: defer NotifyOnPanic\(ctx, "some data"\) example: defer NotifyOnPanic\(ctx, "some data", Tags\{"tag1": "value1"\}\) -## func [NotifyWithExclude]() +## func [NotifyWithExclude]() ```go func NotifyWithExclude(err error, rawData ...interface{}) error @@ -131,7 +131,7 @@ func NotifyWithExclude(err error, rawData ...interface{}) error NotifyWithExclude notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [NotifyWithLevel]() +## func [NotifyWithLevel]() ```go func NotifyWithLevel(err error, level string, rawData ...interface{}) error @@ -140,7 +140,7 @@ func NotifyWithLevel(err error, level string, rawData ...interface{}) error NotifyWithLevel notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [NotifyWithLevelAndSkip]() +## func [NotifyWithLevelAndSkip]() ```go func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interface{}) error @@ -149,7 +149,7 @@ func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interf NotifyWithLevelAndSkip notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify skip: skip stack frames when notify error level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata -## func [SetEnvironment]() +## func [SetEnvironment]() ```go func SetEnvironment(env string) @@ -158,7 +158,7 @@ func SetEnvironment(env string) SetEnvironment sets the environment. The environment is used to distinguish errors occurring in different -## func [SetHostname]() +## func [SetHostname]() ```go func SetHostname(name string) @@ -176,7 +176,7 @@ func SetMaxAsyncNotifications(n int) SetMaxAsyncNotifications sets the maximum number of concurrent async notification goroutines. When the limit is reached, new async notifications are dropped to prevent goroutine explosion under sustained error bursts. Default is 20. The first successful call wins; subsequent calls are no\-ops. It is safe to call concurrently with NotifyAsync. -## func [SetRelease]() +## func [SetRelease]() ```go func SetRelease(rel string) @@ -185,7 +185,7 @@ func SetRelease(rel string) SetRelease sets the release tag. The release tag is used to group errors together by release. -## func [SetServerRoot]() +## func [SetServerRoot]() ```go func SetServerRoot(path string) @@ -203,7 +203,7 @@ func SetTraceHeaderName(name string) SetTraceHeaderName sets the header name for trace id default is x\-trace\-id -## func [SetTraceIDValidator]() +## func [SetTraceIDValidator]() ```go func SetTraceIDValidator(fn func(string) string) @@ -212,7 +212,7 @@ func SetTraceIDValidator(fn func(string) string) SetTraceIDValidator sets a custom trace ID validation function. The function receives a raw trace ID and must return the sanitized version. Returning an empty string triggers the standard trace ID resolution flow \(existing ctx → gRPC metadata → OTEL span trace ID → generate UUID\), not direct generation. Set to nil to disable validation entirely \(not recommended\). Must be called during init — not safe for concurrent use. -## func [SetTraceId]() +## func [SetTraceId]() ```go func SetTraceId(ctx context.Context) context.Context @@ -221,7 +221,7 @@ func SetTraceId(ctx context.Context) context.Context SetTraceId updates the traceID based on context values if no trace id is found then it will create one and update the context You should use the context returned by this function instead of the one passed -## func [SetTraceIdWithValue]() +## func [SetTraceIdWithValue]() ```go func SetTraceIdWithValue(ctx context.Context) (context.Context, string) @@ -230,7 +230,7 @@ func SetTraceIdWithValue(ctx context.Context) (context.Context, string) SetTraceIdWithValue is like SetTraceId but also returns the resolved trace ID, avoiding a separate GetTraceId call. Callers must use the returned context, not the original ctx, so the stored trace ID is preserved in options and log context. -## func [UpdateTraceId]() +## func [UpdateTraceId]() ```go func UpdateTraceId(ctx context.Context, traceID string) context.Context diff --git a/stdlib.go b/stdlib.go new file mode 100644 index 0000000..3f5dd3d --- /dev/null +++ b/stdlib.go @@ -0,0 +1,76 @@ +package errors + +import stderrors "errors" + +// --- Re-exports from the standard library errors package --- +// +// These allow github.com/go-coldbrew/errors to be used as a drop-in +// replacement for the standard "errors" package. + +// Is reports whether any error in err's tree matches target. +// +// An error is considered a match if it is equal to the target or if +// it implements an Is(error) bool method such that Is(target) returns true. +// +// Re-exported from the standard library errors package. +func Is(err, target error) bool { + return stderrors.Is(err, target) +} + +// As finds the first error in err's tree that matches target, +// and if one is found, sets target to that error value and returns true. +// +// Re-exported from the standard library errors package. +func As(err error, target any) bool { + return stderrors.As(err, target) +} + +// Unwrap returns the result of calling the Unwrap method on err, +// if err's type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +// +// Re-exported from the standard library errors package. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. Join returns nil if every value +// in errs is nil. +// +// Re-exported from the standard library errors package. +func Join(errs ...error) error { + return stderrors.Join(errs...) +} + +// ErrUnsupported indicates that a requested operation cannot be performed, +// because it is unsupported. +// +// Re-exported from the standard library errors package. +var ErrUnsupported = stderrors.ErrUnsupported + +// maxCauseDepth is the upper bound on Unwrap iterations in [Cause] +// to guard against cyclic Unwrap chains. +const maxCauseDepth = 1024 + +// Cause walks the [Unwrap] chain of err and returns the innermost +// (root cause) error. If err does not implement Unwrap, err itself +// is returned. If err is nil, nil is returned. +// +// For [ErrorExt] errors, this produces the same result as calling +// the Cause method, but this function works on any error that +// implements the standard Unwrap interface. +// +// Note: for multi-errors (errors implementing Unwrap() []error, such as +// those created by [Join]), the single-error Unwrap returns nil, so +// Cause returns the multi-error itself. +func Cause(err error) error { + for range maxCauseDepth { + unwrapped := stderrors.Unwrap(err) + if unwrapped == nil { + return err + } + err = unwrapped + } + return err +} diff --git a/stdlib_go126.go b/stdlib_go126.go new file mode 100644 index 0000000..98be135 --- /dev/null +++ b/stdlib_go126.go @@ -0,0 +1,14 @@ +//go:build go1.26 + +package errors + +import stderrors "errors" + +// AsType finds the first error in err's tree that matches the type E, +// and if one is found, returns that error value and true. +// Otherwise, it returns the zero value of E and false. +// +// Re-exported from the standard library errors package (requires Go 1.26+). +func AsType[E error](err error) (E, bool) { + return stderrors.AsType[E](err) +}