Skip to content

Commit 70928cb

Browse files
committed
feat: add complete slog type coverage and deprecate Value function
This commit introduces dedicated functions for all slog attribute types, including Int64, Uint64, Float64, and Any, enhancing the flexibility of error handling. The Value function is deprecated in favor of Any for better consistency. Additionally, tests are added to ensure the new attribute options work as expected.
1 parent 77e53e3 commit 70928cb

4 files changed

Lines changed: 202 additions & 22 deletions

File tree

MIGRATION.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,34 @@ func TestMyError(t *testing.T) {
173173

174174
## New Features
175175

176-
### 1. Grouped Attributes
176+
### 1. Complete slog Type Coverage
177+
178+
All slog attribute types are now supported with dedicated functions:
179+
180+
```go
181+
// New in v0.5.0
182+
errors.Int64(key string, value int64)
183+
errors.Uint64(key string, value uint64)
184+
errors.Float64(key string, value float64)
185+
errors.Any(key string, value interface{}) // Replaces Value
186+
187+
// Deprecated
188+
errors.Value(key string, value interface{}) // Use Any instead
189+
```
190+
191+
Example:
192+
193+
```go
194+
err := errors.Wrap(
195+
dbErr,
196+
errors.Int64("timestamp", time.Now().Unix()),
197+
errors.Uint64("bytes_processed", uint64(1024*1024)),
198+
errors.Float64("cpu_usage", 0.75),
199+
errors.Any("metadata", map[string]string{"region": "us-west"}),
200+
)
201+
```
202+
203+
### 2. Grouped Attributes
177204

178205
Group related attributes together for better structure:
179206

@@ -214,7 +241,7 @@ err := errors.Wrap(
214241
// query.duration: 150ms
215242
```
216243

217-
### 2. Direct slog.Attr Usage
244+
### 3. Direct slog.Attr Usage
218245

219246
Pass `slog.Attr` directly for maximum flexibility:
220247

@@ -229,7 +256,7 @@ err := errors.Wrap(
229256
)
230257
```
231258

232-
### 3. Multiple Attributes at Once
259+
### 4. Multiple Attributes at Once
233260

234261
```go
235262
commonAttrs := []slog.Attr{
@@ -240,7 +267,7 @@ commonAttrs := []slog.Attr{
240267
err := errors.Wrap(err, errors.WithAttrs(commonAttrs...))
241268
```
242269

243-
### 4. slog.LogValuer Implementation
270+
### 5. slog.LogValuer Implementation
244271

245272
Errors automatically work with slog:
246273

README.md

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,26 @@ err := errors.Wrap(
133133
)
134134
```
135135

136-
You can also use `errors.Attr()` to pass `slog.Attr` directly:
136+
You can use all slog attribute types directly:
137+
138+
```golang
139+
err := errors.Wrap(
140+
err,
141+
errors.Int64("timestamp", time.Now().Unix()),
142+
errors.Uint64("bytes_written", uint64(1024*1024*500)),
143+
errors.Float64("cpu_usage", 0.85),
144+
errors.Any("metadata", map[string]interface{}{
145+
"version": "v1.2.3",
146+
"region": "us-west-1",
147+
}),
148+
)
149+
```
150+
151+
Or pass `slog.Attr` directly for maximum flexibility:
137152

138153
```golang
139154
err := errors.Wrap(
140155
err,
141-
errors.Attr(slog.Int64("timestamp", time.Now().Unix())),
142156
errors.Attr(slog.Group("metadata",
143157
slog.String("version", "v1.2.3"),
144158
slog.Bool("production", true),
@@ -338,25 +352,34 @@ attrs := errors.Attrs(wrapped)
338352

339353
## Available attribute options
340354

341-
The package provides convenience functions for creating attributes:
355+
The package provides convenience functions for creating attributes that correspond to all slog types:
342356

343357
```golang
358+
// Basic types
344359
errors.Bool(key string, value bool)
345-
errors.Int(key string, value int)
346-
errors.Uint(key string, value uint)
347-
errors.Float(key string, value float64)
360+
errors.Int(key string, value int) // Converted to int64
361+
errors.Int64(key string, value int64)
362+
errors.Uint(key string, value uint) // Uses slog.Any
363+
errors.Uint64(key string, value uint64)
364+
errors.Float(key string, value float64) // Alias for Float64
365+
errors.Float64(key string, value float64)
348366
errors.String(key string, value string)
349-
errors.Stringer(key string, value fmt.Stringer)
350-
errors.Strings(key string, values []string)
351-
errors.Value(key string, value interface{})
367+
368+
// Complex types
369+
errors.Stringer(key string, value fmt.Stringer) // Converted to string
370+
errors.Strings(key string, values []string) // Uses slog.Any
371+
errors.Any(key string, value interface{}) // For any type
352372
errors.Time(key string, value time.Time)
353373
errors.Duration(key string, value time.Duration)
354-
errors.JSON(key string, value json.RawMessage)
374+
errors.JSON(key string, value json.RawMessage) // Uses slog.Any
375+
376+
// slog-specific options
377+
errors.Attr(attr slog.Attr) // Add any slog.Attr directly
378+
errors.WithAttrs(attrs ...slog.Attr) // Add multiple slog.Attr values
379+
errors.Group(key string, attrs ...slog.Attr) // Create a grouped attribute
355380

356-
// New slog-specific options
357-
errors.Attr(attr slog.Attr) // Add any slog.Attr directly
358-
errors.WithAttrs(attrs ...slog.Attr) // Add multiple slog.Attr values
359-
errors.Group(key string, attrs ...slog.Attr) // Create a grouped attribute
381+
// Deprecated
382+
errors.Value(key string, value interface{}) // Use Any instead
360383
```
361384

362385
## Contributing

options.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,20 @@ func Bool(key string, value bool) Option {
5050
}
5151

5252
// Int returns an Option that adds an integer attribute to the error.
53+
// The value is converted to int64 for slog compatibility.
5354
func Int(key string, value int) Option {
5455
return func(options *Options) {
5556
options.addAttr(slog.Int(key, value))
5657
}
5758
}
5859

60+
// Int64 returns an Option that adds an int64 attribute to the error.
61+
func Int64(key string, value int64) Option {
62+
return func(options *Options) {
63+
options.addAttr(slog.Int64(key, value))
64+
}
65+
}
66+
5967
// Uint returns an Option that adds an unsigned integer attribute to the error.
6068
// Note: slog doesn't have a native Uint type, so this uses Any().
6169
func Uint(key string, value uint) Option {
@@ -64,13 +72,26 @@ func Uint(key string, value uint) Option {
6472
}
6573
}
6674

67-
// Float returns an Option that adds a float64 attribute to the error.
68-
func Float(key string, value float64) Option {
75+
// Uint64 returns an Option that adds a uint64 attribute to the error.
76+
func Uint64(key string, value uint64) Option {
77+
return func(options *Options) {
78+
options.addAttr(slog.Uint64(key, value))
79+
}
80+
}
81+
82+
// Float64 returns an Option that adds a float64 attribute to the error.
83+
func Float64(key string, value float64) Option {
6984
return func(options *Options) {
7085
options.addAttr(slog.Float64(key, value))
7186
}
7287
}
7388

89+
// Float returns an Option that adds a float64 attribute to the error.
90+
// Alias for Float64 for backward compatibility.
91+
func Float(key string, value float64) Option {
92+
return Float64(key, value)
93+
}
94+
7495
// String returns an Option that adds a string attribute to the error.
7596
func String(key string, value string) Option {
7697
return func(options *Options) {
@@ -92,13 +113,29 @@ func Strings(key string, values []string) Option {
92113
}
93114
}
94115

95-
// Value returns an Option that adds an arbitrary value attribute to the error.
96-
func Value(key string, value interface{}) Option {
116+
// Any returns an Option that adds an arbitrary value attribute to the error.
117+
// This is the most flexible option and can handle any type that slog.Any supports.
118+
//
119+
// Example:
120+
//
121+
// err := errors.Wrap(err,
122+
// errors.Any("metadata", map[string]string{"version": "v1.0"}),
123+
// errors.Any("items", []int{1, 2, 3}),
124+
// )
125+
func Any(key string, value interface{}) Option {
97126
return func(options *Options) {
98127
options.addAttr(slog.Any(key, value))
99128
}
100129
}
101130

131+
// Value returns an Option that adds an arbitrary value attribute to the error.
132+
//
133+
// Deprecated: Use Any instead. Value is kept for backward compatibility
134+
// but will be removed in a future version.
135+
func Value(key string, value interface{}) Option {
136+
return Any(key, value)
137+
}
138+
102139
// Time returns an Option that adds a time.Time attribute to the error.
103140
func Time(key string, value time.Time) Option {
104141
return func(options *Options) {

options_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package errors_test
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/muonsoft/errors"
8+
)
9+
10+
func TestNewAttributeOptions(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
err error
14+
expected interface{}
15+
}{
16+
{
17+
name: "Int64",
18+
err: errors.Wrap(errors.New("test"), errors.Int64("key", 9223372036854775807)),
19+
expected: int64(9223372036854775807),
20+
},
21+
{
22+
name: "Uint64",
23+
err: errors.Wrap(errors.New("test"), errors.Uint64("key", 18446744073709551615)),
24+
expected: uint64(18446744073709551615),
25+
},
26+
{
27+
name: "Float64",
28+
err: errors.Wrap(errors.New("test"), errors.Float64("key", 3.14159)),
29+
expected: 3.14159,
30+
},
31+
{
32+
name: "Any with struct",
33+
err: errors.Wrap(errors.New("test"), errors.Any("key", struct{ Name string }{"test"})),
34+
expected: struct{ Name string }{"test"},
35+
},
36+
{
37+
name: "Any with map",
38+
err: errors.Wrap(errors.New("test"), errors.Any("key", map[string]int{"count": 42})),
39+
expected: map[string]int{"count": 42},
40+
},
41+
}
42+
43+
for _, test := range tests {
44+
t.Run(test.name, func(t *testing.T) {
45+
attrs := errors.Attrs(test.err)
46+
if len(attrs) == 0 {
47+
t.Fatalf("expected %#v to have attributes", test.err)
48+
}
49+
50+
var found bool
51+
var value interface{}
52+
for _, attr := range attrs {
53+
if attr.Key == "key" {
54+
value = attr.Value.Any()
55+
found = true
56+
break
57+
}
58+
}
59+
60+
if !found {
61+
t.Fatalf("expected %#v to have attribute with key 'key'", test.err)
62+
}
63+
64+
if !reflect.DeepEqual(value, test.expected) {
65+
t.Errorf("want value %v (%T), got %v (%T)", test.expected, test.expected, value, value)
66+
}
67+
})
68+
}
69+
}
70+
71+
func TestValueDeprecated(t *testing.T) {
72+
// Test that Value still works but behaves the same as Any
73+
errWithValue := errors.Wrap(errors.New("test"), errors.Value("key", "test-value"))
74+
errWithAny := errors.Wrap(errors.New("test"), errors.Any("key", "test-value"))
75+
76+
attrsValue := errors.Attrs(errWithValue)
77+
attrsAny := errors.Attrs(errWithAny)
78+
79+
if len(attrsValue) != 1 || len(attrsAny) != 1 {
80+
t.Fatalf("expected both errors to have exactly 1 attribute")
81+
}
82+
83+
valueAttr := attrsValue[0]
84+
anyAttr := attrsAny[0]
85+
86+
if valueAttr.Key != anyAttr.Key {
87+
t.Errorf("expected same key, got Value=%s, Any=%s", valueAttr.Key, anyAttr.Key)
88+
}
89+
90+
if valueAttr.Value.Any() != anyAttr.Value.Any() {
91+
t.Errorf("expected same value, got Value=%v, Any=%v", valueAttr.Value.Any(), anyAttr.Value.Any())
92+
}
93+
}

0 commit comments

Comments
 (0)