Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ type Builder struct {
options []Option
}

// NewBuilder creates a new Builder
// NewBuilder creates a new Builder.
// A Builder is useful for creating multiple errors that share a common context, such as a request ID or service name, without repeatedly specifying the same options.
func NewBuilder(options ...Option) *Builder {
return &Builder{
options: options,
Expand Down
22 changes: 22 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package goerr_test

import (
"fmt"
"io"
"testing"

"github.com/m-mizutani/goerr/v2"
Expand Down Expand Up @@ -30,3 +32,23 @@ func TestBuilderWrap(t *testing.T) {
t.Errorf("Unexpected cause: %v", err.Unwrap().Error())
}
}

func ExampleNewBuilder() {
// Create a builder with common context for a request.
builder := goerr.NewBuilder(
goerr.Value("service", "auth-service"),
goerr.Value("request_id", "req-9876"),
)

// Use the builder to create errors.
err1 := builder.New("user not found")
err2 := builder.Wrap(io.EOF, "failed to read body")

// The context from the builder is automatically included.
fmt.Println(goerr.Values(err1)["service"])
fmt.Println(goerr.Values(err2)["request_id"])

// Output:
// auth-service
// req-9876
}
11 changes: 6 additions & 5 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func V(key string, value any) Option {
}

// ID sets an error ID for Is() comparison.
// Empty string ("") is treated as an invalid ID and will not be used for comparison.
// When ID is set, errors.Is() will compare errors by ID instead of pointer equality.
// When an ID is set, errors.Is() will compare errors by their ID string instead of by pointer equality. This allows for creating sentinel-like errors that can be matched even if they are wrapped.
// An empty string ("") is treated as an invalid ID and will not be used for comparison.
func ID(id string) Option {
return func(err *Error) {
err.id = id
Expand Down Expand Up @@ -443,9 +443,10 @@ func (x *Error) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Printable())
}

// With adds contextual information to an error without modifying the original.
// If err is goerr.Error, creates a new error preserving existing stacktrace and adds only new values/tags.
// If err is not goerr.Error, wraps it with new stacktrace and adds values/tags.
// With adds contextual information to an error without modifying the original. It is useful when you want to enrich an error with more context in a middleware or a higher-level function without altering the original error value.
//
// If err is a *goerr.Error, it creates a new *Error that preserves the original stacktrace and adds the new options.
// If err is a standard error, it wraps the error in a new *goerr.Error with a new stacktrace and adds the options.
func With(err error, options ...Option) *Error {
if err == nil {
return nil
Expand Down
11 changes: 11 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,3 +792,14 @@ func TestWith_KeyPrecedence(t *testing.T) {
}
})
}

func ExampleID() {
var ErrPermission = goerr.New("permission denied", goerr.ID("permission"))

err := goerr.Wrap(ErrPermission, "failed to open file")

if errors.Is(err, ErrPermission) {
fmt.Println("Error is a permission error")
}
// Output: Error is a permission error
}
2 changes: 1 addition & 1 deletion tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (t tag) Format(s fmt.State, verb rune) {

// WithTags adds tags to the error. The tags are used to categorize errors.
//
// Deprecated: Use goerr.Tag instead.
// Deprecated: Use the goerr.Tag option with goerr.New() or goerr.Wrap() instead.
func (x *Error) WithTags(tags ...tag) *Error {
for _, tag := range tags {
x.tags[tag] = struct{}{}
Expand Down
3 changes: 2 additions & 1 deletion typed_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ type TypedKey[T any] struct {
name string
}

// NewTypedKey creates a new typed key with the given name
// NewTypedKey creates a new type-safe key with the given name.
// This key can then be used with TV() and GetTypedValue() to attach and retrieve strongly-typed values from an error, providing compile-time safety.
func NewTypedKey[T any](name string) TypedKey[T] {
return TypedKey[T]{name: name}
}
Expand Down
26 changes: 26 additions & 0 deletions typed_values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,3 +683,29 @@ func TestTypedValueClone(t *testing.T) {
}
})
}

func ExampleNewTypedKey() {
// Define typed keys at the package level for reuse.
var UserIDKey = goerr.NewTypedKey[string]("user_id")
var RequestIDKey = goerr.NewTypedKey[int]("request_id")

// Attach typed values when creating an error.
err := goerr.New("request failed",
goerr.TV(UserIDKey, "blue"),
goerr.TV(RequestIDKey, 12345),
)

// Retrieve the typed value later.
if userID, ok := goerr.GetTypedValue(err, UserIDKey); ok {
// The retrieved value has the correct type (string), no assertion needed.
fmt.Printf("User ID: %s\n", userID)
}

if reqID, ok := goerr.GetTypedValue(err, RequestIDKey); ok {
fmt.Printf("Request ID: %d\n", reqID)
}

// Output:
// User ID: blue
// Request ID: 12345
}
Loading