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
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ if zerrErr, ok := err.(*zerr.Error); ok {
}
```

### Accessing Metadata

You can access the structured metadata attached to errors using the `Metadata()` method:

```go
// Create an error with metadata
err := zerr.New("database error")
err = zerr.With(err, "table", "users")
err = zerr.With(err, "operation", "insert")
err = zerr.With(err, "user_id", 12345)

// Access metadata (requires type assertion to *zerr.Error)
if zerrErr, ok := err.(*zerr.Error); ok {
metadata := zerrErr.Metadata()
// metadata is map[string]any{"table": "users", "operation": "insert", "user_id": 12345}

// Access individual values
if table, exists := metadata["table"]; exists {
fmt.Printf("Table: %v\n", table)
}
}
```

The `Metadata()` method returns a copy of the error's metadata as a `map[string]any`. It returns an empty map if there is no metadata attached to the error. The returned map is a copy, ensuring that modifications to it do not affect the internal state of the error.

### Stack Traces

Capture stack traces easily using the global `Stack` helper.
Expand Down Expand Up @@ -136,4 +161,4 @@ paths.

## License

MIT
MIT
14 changes: 14 additions & 0 deletions zerr.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,20 @@ func (e *Error) WithStack() *Error {
}
}

// Metadata returns a copy of the error's metadata as a map.
// Returns an empty map if there is no metadata attached to the error.
func (e *Error) Metadata() map[string]any {
if len(e.metadata) == 0 {
return make(map[string]any)
}

result := make(map[string]any, len(e.metadata))
for _, meta := range e.metadata {
result[meta.key.Value()] = meta.value
}
return result
}

// Error implements the error interface.
func (e *Error) Error() string {
if e.cause == nil {
Expand Down
74 changes: 73 additions & 1 deletion zerr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestWithMultipleMetadata(t *testing.T) {
t.Errorf("Expected 3 metadata items, got %d", len(withErr.metadata))
}

expected := map[string]interface{}{
expected := map[string]any{
"key1": "value1",
"key2": 42,
"key3": true,
Expand Down Expand Up @@ -760,3 +760,75 @@ func ExampleWith() {
fmt.Println(err.Error())
// Output: database error
}

func TestMetadata(t *testing.T) {
// Test with no metadata
testErr := New("test")
err, ok := testErr.(*Error)
if !ok {
t.Fatalf("Expected *Error type, got %T", testErr)
}

metadata := err.Metadata()
if metadata == nil {
t.Error("Metadata should not return nil")
}
if len(metadata) != 0 {
t.Errorf("Expected empty metadata, got %d items", len(metadata))
}

// Test with single metadata
withErr := err.With("key", "value")
metadata = withErr.Metadata()
if len(metadata) != 1 {
t.Errorf("Expected 1 metadata item, got %d", len(metadata))
}
if metadata["key"] != "value" {
t.Errorf("Expected metadata[key] = 'value', got %v", metadata["key"])
}

// Test with multiple metadata
multiErr := err.With("key1", "value1").With("key2", 42).With("key3", true)
metadata = multiErr.Metadata()
if len(metadata) != 3 {
t.Errorf("Expected 3 metadata items, got %d", len(metadata))
}

expected := map[string]any{
"key1": "value1",
"key2": 42,
"key3": true,
}

for key, expectedValue := range expected {
if value, exists := metadata[key]; !exists {
t.Errorf("Missing metadata key: %s", key)
} else if value != expectedValue {
t.Errorf("For key %s, expected %v, got %v", key, expectedValue, value)
}
}
}

func TestMetadataImmutability(t *testing.T) {
testErr := New("test")
err, ok := testErr.(*Error)
if !ok {
t.Fatalf("Expected *Error type, got %T", testErr)
}

withErr := err.With("key", "value")
metadata := withErr.Metadata()

// Modify the returned map
metadata["new_key"] = "new_value"
metadata["key"] = "modified_value"

// Get metadata again - should be unchanged
metadata2 := withErr.Metadata()
if metadata2["key"] != "value" {
t.Error("Metadata should be immutable - modifying returned map should not affect original")
}
if _, exists := metadata2["new_key"]; exists {
t.Error("Metadata should be immutable - adding keys to returned map should not affect original")
}
}
Loading