+ + + +```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 +``` + +
++ + + +```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 +``` + +
++ + + +```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 +``` + +
++ + + +```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 +``` + +
++ + + +```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 +``` + +
+
-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](