Skip to content

GODRIVER-1627 Export keys slice from bsoncodec.DecodeError #421

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

Merged
merged 1 commit into from
Jun 11, 2020
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
15 changes: 9 additions & 6 deletions bson/bsoncodec/default_value_decoders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3385,11 +3385,11 @@ func TestDefaultValueDecoders(t *testing.T) {
Foo string
}
emptyInterfaceStructErr := &DecodeError{
keys: []string{"foo(field Foo)"},
keys: []string{"foo"},
wrapped: decodeValueError,
}
stringStructErr := &DecodeError{
keys: []string{"foo(field Foo)"},
keys: []string{"foo"},
wrapped: ErrNoDecoder{reflect.TypeOf("")},
}

Expand Down Expand Up @@ -3420,7 +3420,7 @@ func TestDefaultValueDecoders(t *testing.T) {
RegisterTypeDecoder(tEmpty, ValueDecoderFunc(emptyInterfaceErrorDecode)).
Build()
nestedErr := &DecodeError{
keys: []string{"fourth(field Fourth)", "1", "third(field Third)", "randomKey", "second(field Second)", "first(field First)"},
keys: []string{"fourth", "1", "third", "randomKey", "second", "first"},
wrapped: decodeValueError,
}

Expand Down Expand Up @@ -3546,9 +3546,12 @@ func TestDefaultValueDecoders(t *testing.T) {

decodeErr, ok := err.(*DecodeError)
assert.True(t, ok, "expected DecodeError, got %v of type %T", err, err)
keyPattern := "foo(field Foo).bar(field Bar)"
assert.True(t, strings.Contains(decodeErr.Error(), keyPattern),
"expected error %v to contain key pattern %s", decodeErr, keyPattern)
expectedKeys := []string{"foo", "bar"}
assert.Equal(t, expectedKeys, decodeErr.Keys(), "expected keys slice %v, got %v", expectedKeys,
decodeErr.Keys())
keyPath := strings.Join(expectedKeys, ".")
assert.True(t, strings.Contains(decodeErr.Error(), keyPath),
"expected error %v to contain key pattern %s", decodeErr, keyPath)
})
})
}
Expand Down
33 changes: 16 additions & 17 deletions bson/bsoncodec/struct_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package bsoncodec

import (
"bytes"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -39,16 +38,21 @@ func (de *DecodeError) Unwrap() error {
// Error implements the error interface.
func (de *DecodeError) Error() string {
// The keys are stored in reverse order because the de.keys slice is builtup while propagating the error up the
// stack of BSON keys. Reverse the keys and join them with "." as they're reversed.
var keyPattern bytes.Buffer
// stack of BSON keys, so we call de.Keys(), which reverses them.
keyPath := strings.Join(de.Keys(), ".")
return fmt.Sprintf("error decoding key %s: %v", keyPath, de.wrapped)
}

// Keys returns the BSON key path that caused an error as a slice of strings. The keys in the slice are in top-down
// order. For example, if the document being unmarshalled was {a: {b: {c: 1}}} and the value for c was supposed to be
// a string, the keys slice will be ["a", "b", "c"].
func (de *DecodeError) Keys() []string {
reversedKeys := make([]string, 0, len(de.keys))
for idx := len(de.keys) - 1; idx >= 0; idx-- {
keyPattern.WriteString(de.keys[idx])
if idx != 0 {
keyPattern.WriteByte('.')
}
reversedKeys = append(reversedKeys, de.keys[idx])
}

return fmt.Sprintf("error decoding key %s: %v", keyPattern.String(), de.wrapped)
return reversedKeys
}

// Zeroer allows custom struct types to implement a report of zero
Expand Down Expand Up @@ -206,11 +210,6 @@ func newDecodeError(key string, original error) error {
return de
}

func newDecodeErrorFromFieldDescription(fd fieldDescription, original error) error {
fullName := fmt.Sprintf("%s(field %s)", fd.name, fd.fieldName)
return newDecodeError(fullName, original)
}

// DecodeValue implements the Codec interface.
// By default, map types in val will not be cleared. If a map has existing key/value pairs, it will be extended with the new ones from vr.
// For slices, the decoder will set the length of the slice to zero and append all elements. The underlying array will not be cleared.
Expand Down Expand Up @@ -320,7 +319,7 @@ func (sc *StructCodec) DecodeValue(r DecodeContext, vr bsonrw.ValueReader, val r

if !field.CanSet() { // Being settable is a super set of being addressable.
innerErr := fmt.Errorf("field %v is not settable", field)
return newDecodeErrorFromFieldDescription(fd, innerErr)
return newDecodeError(fd.name, innerErr)
}
if field.Kind() == reflect.Ptr && field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
Expand All @@ -329,19 +328,19 @@ func (sc *StructCodec) DecodeValue(r DecodeContext, vr bsonrw.ValueReader, val r

dctx := DecodeContext{Registry: r.Registry, Truncate: fd.truncate || r.Truncate}
if fd.decoder == nil {
return newDecodeErrorFromFieldDescription(fd, ErrNoDecoder{Type: field.Elem().Type()})
return newDecodeError(fd.name, ErrNoDecoder{Type: field.Elem().Type()})
}

if decoder, ok := fd.decoder.(ValueDecoder); ok {
err = decoder.DecodeValue(dctx, vr, field.Elem())
if err != nil {
return newDecodeErrorFromFieldDescription(fd, err)
return newDecodeError(fd.name, err)
}
continue
}
err = fd.decoder.DecodeValue(dctx, vr, field)
if err != nil {
return newDecodeErrorFromFieldDescription(fd, err)
return newDecodeError(fd.name, err)
}
}

Expand Down