Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add V func to access a kv value from an error #2

Merged
merged 1 commit into from
Mar 18, 2024
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
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
Loading