Skip to content

Commit

Permalink
feat: add V func to access a kv value from an error
Browse files Browse the repository at this point in the history
  • Loading branch information
jsteenb2 committed Mar 17, 2024
1 parent e675074 commit 1161e21
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
16 changes: 16 additions & 0 deletions err_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ func (err *e) Unwrap() error {
return err.wrappedErr
}

func (err *e) V(key string) (any, bool) {
for err := error(err); err != nil; err = errors.Unwrap(err) {
ee, ok := err.(*e)
if !ok {
continue
}

for _, kv := range ee.kvs {
if kv.K == key {
return kv.V, true
}
}
}
return nil, false
}

func (err *e) stackTrace() StackFrames {
var out StackFrames
for err := error(err); err != nil; err = errors.Unwrap(err) {
Expand Down
35 changes: 35 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,38 @@ func StackTrace(err error) StackFrames {
}
return ee.stackTrace()
}

// V returns a typed value for the kvs of an error. Type conversion
// can be used to convert the output value. We do not distinguish
// between a purposeful <nil> value and key not found. With the
// single return param, we can do the following to convert it to a
// more specific type:
//
// err := errors.New("simple msg", errors.KVs("int", 1))
// i, ok := errors.V(err, "int).(int)
//
// Note: this will take the first matching key. If you are interested
// in obtaining a key's value from a wrapped error collides with a
// parent's key value, then you can manually unwrap the error and call V
// on it to skip the parent field.
//
// TODO:
// - food for thought, we could change the V funcs signature
// to allow for a generic type to be provided, however... it
// feels both premature and limiting in the event you don't
// care about the type. If we get an ask for that, we can provide
// guidance for this via the comment above and perhaps some example
// code.
func V(err error, key string) any {
if err == nil {
return nil
}

fielder, ok := err.(interface{ V(key string) (any, bool) })
if !ok {
return nil
}

raw, _ := fielder.V(key)
return raw
}
35 changes: 35 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,33 @@ func Test_Errors(t *testing.T) {
}
}

func TestV(t *testing.T) {
type foo struct {
i int
}

t.Run("key val pairs are should be accessible", func(t *testing.T) {
err := errors.New("simple msg", errors.KVs("bool", true, "str", "string", "float", 3.14, "int", 1, "foo", foo{i: 3}))

eqV(t, err, "bool", true)
eqV(t, err, "str", "string")
eqV(t, err, "float", 3.14)
eqV(t, err, "int", 1)
eqV(t, err, "foo", foo{i: 3})

if v := errors.V(err, "non existent"); v != nil {
t.Errorf("unexpected value returned:\n\t\tgot:\t%#v", v)
}
})

t.Run("when parent error kv pair collides with wrapped error will take parent kv pair", func(t *testing.T) {
err := errors.New("simple msg", errors.KVs("str", "initial"))
err = errors.Wrap(err, errors.KVs("str", "wrapped"))

eqV(t, err, "str", "wrapped")
})
}

func eq[T comparable](t *testing.T, want, got T) bool {
t.Helper()

Expand All @@ -194,6 +221,14 @@ func eq[T comparable](t *testing.T, want, got T) bool {
return matches
}

func eqV[T comparable](t *testing.T, err error, key string, want T) bool {
t.Helper()

got, ok := errors.V(err, key).(T)
must(t, eq(t, true, ok))
return eq(t, want, got)
}

func eqFields(t *testing.T, want, got []any) bool {
t.Helper()

Expand Down

0 comments on commit 1161e21

Please sign in to comment.