diff --git a/bson/bsoncodec.go b/bson/bsoncodec.go index 860a6b82af..ad1d4a8ded 100644 --- a/bson/bsoncodec.go +++ b/bson/bsoncodec.go @@ -309,17 +309,10 @@ type decodeAdapter struct { var _ ValueDecoder = decodeAdapter{} var _ typeDecoder = decodeAdapter{} -// decodeTypeOrValue calls decoder.decodeType is decoder is a typeDecoder. Otherwise, it allocates a new element of type -// t and calls decoder.DecodeValue on it. -func decodeTypeOrValue(decoder ValueDecoder, dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { - td, _ := decoder.(typeDecoder) - return decodeTypeOrValueWithInfo(decoder, td, dc, vr, t, true) -} - -func decodeTypeOrValueWithInfo(vd ValueDecoder, td typeDecoder, dc DecodeContext, vr ValueReader, t reflect.Type, convert bool) (reflect.Value, error) { - if td != nil { +func decodeTypeOrValueWithInfo(vd ValueDecoder, dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { + if td, _ := vd.(typeDecoder); td != nil { val, err := td.decodeType(dc, vr, t) - if err == nil && convert && val.Type() != t { + if err == nil && val.Type() != t { // This conversion step is necessary for slices and maps. If a user declares variables like: // // type myBool bool diff --git a/bson/decoder_test.go b/bson/decoder_test.go index 8fe8d07480..dbef3e7fb0 100644 --- a/bson/decoder_test.go +++ b/bson/decoder_test.go @@ -39,6 +39,162 @@ func TestBasicDecode(t *testing.T) { } } +func TestDecodingInterfaces(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + stub func() ([]byte, interface{}, func(*testing.T)) + } + testCases := []testCase{ + { + name: "struct with interface containing a concrete value", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Value interface{} + } + var value string + + data := docToBytes(struct { + Value string + }{ + Value: "foo", + }) + + receiver := testStruct{&value} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", value) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing a struct", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type demo struct { + Data string + } + + type testStruct struct { + Value interface{} + } + var value demo + + data := docToBytes(struct { + Value demo + }{ + Value: demo{"foo"}, + }) + + receiver := testStruct{&value} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", value.Data) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing a slice", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values interface{} + } + var values []string + + data := docToBytes(struct { + Values []string + }{ + Values: []string{"foo", "bar"}, + }) + + receiver := testStruct{&values} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, []string{"foo", "bar"}, values) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing an array", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values interface{} + } + var values [2]string + + data := docToBytes(struct { + Values []string + }{ + Values: []string{"foo", "bar"}, + }) + + receiver := testStruct{&values} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, [2]string{"foo", "bar"}, values) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface array containing concrete values", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values [3]interface{} + } + var str string + var i, j int + + data := docToBytes(struct { + Values []interface{} + }{ + Values: []interface{}{"foo", 42, nil}, + }) + + receiver := testStruct{[3]interface{}{&str, &i, &j}} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", str) + assert.Equal(t, 42, i) + assert.Equal(t, 0, j) + assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver) + } + + return data, &receiver, check + }, + }, + } + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + data, receiver, check := tc.stub() + got := reflect.ValueOf(receiver).Elem() + vr := NewValueReader(data) + reg := DefaultRegistry + decoder, err := reg.LookupDecoder(got.Type()) + noerr(t, err) + err = decoder.DecodeValue(DecodeContext{Registry: reg}, vr, got) + noerr(t, err) + check(t) + }) + } +} + func TestDecoderv2(t *testing.T) { t.Parallel() diff --git a/bson/default_value_decoders.go b/bson/default_value_decoders.go index bc8c7b9344..3256f92089 100644 --- a/bson/default_value_decoders.go +++ b/bson/default_value_decoders.go @@ -14,7 +14,6 @@ import ( "net/url" "reflect" "strconv" - "time" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" ) @@ -162,7 +161,6 @@ func (dvd DefaultValueDecoders) DDecodeValue(dc DecodeContext, vr ValueReader, v if err != nil { return err } - tEmptyTypeDecoder, _ := decoder.(typeDecoder) // Use the elements in the provided value if it's non nil. Otherwise, allocate a new D instance. var elems D @@ -181,13 +179,13 @@ func (dvd DefaultValueDecoders) DDecodeValue(dc DecodeContext, vr ValueReader, v return err } - // Pass false for convert because we don't need to call reflect.Value.Convert for tEmpty. - elem, err := decodeTypeOrValueWithInfo(decoder, tEmptyTypeDecoder, dc, elemVr, tEmpty, false) + var v interface{} + err = decoder.DecodeValue(dc, elemVr, reflect.ValueOf(&v).Elem()) if err != nil { return err } - elems = append(elems, E{Key: key, Value: elem.Interface()}) + elems = append(elems, E{Key: key, Value: v}) } val.Set(reflect.ValueOf(elems)) @@ -363,89 +361,6 @@ func (dvd DefaultValueDecoders) IntDecodeValue(dc DecodeContext, vr ValueReader, return nil } -// UintDecodeValue is the ValueDecoderFunc for uint types. -// -// Deprecated: UintDecodeValue is not registered by default. Use UintCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) UintDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { - var i64 int64 - var err error - switch vr.Type() { - case TypeInt32: - i32, err := vr.ReadInt32() - if err != nil { - return err - } - i64 = int64(i32) - case TypeInt64: - i64, err = vr.ReadInt64() - if err != nil { - return err - } - case TypeDouble: - f64, err := vr.ReadDouble() - if err != nil { - return err - } - if !dc.Truncate && math.Floor(f64) != f64 { - return errors.New("UintDecodeValue can only truncate float64 to an integer type when truncation is enabled") - } - if f64 > float64(math.MaxInt64) { - return fmt.Errorf("%g overflows int64", f64) - } - i64 = int64(f64) - case TypeBoolean: - b, err := vr.ReadBoolean() - if err != nil { - return err - } - if b { - i64 = 1 - } - default: - return fmt.Errorf("cannot decode %v into an integer type", vr.Type()) - } - - if !val.CanSet() { - return ValueDecoderError{ - Name: "UintDecodeValue", - Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint}, - Received: val, - } - } - - switch val.Kind() { - case reflect.Uint8: - if i64 < 0 || i64 > math.MaxUint8 { - return fmt.Errorf("%d overflows uint8", i64) - } - case reflect.Uint16: - if i64 < 0 || i64 > math.MaxUint16 { - return fmt.Errorf("%d overflows uint16", i64) - } - case reflect.Uint32: - if i64 < 0 || i64 > math.MaxUint32 { - return fmt.Errorf("%d overflows uint32", i64) - } - case reflect.Uint64: - if i64 < 0 { - return fmt.Errorf("%d overflows uint64", i64) - } - case reflect.Uint: - if i64 < 0 || int64(uint(i64)) != i64 { // Can we fit this inside of an uint - return fmt.Errorf("%d overflows uint", i64) - } - default: - return ValueDecoderError{ - Name: "UintDecodeValue", - Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint}, - Received: val, - } - } - - val.SetUint(uint64(i64)) - return nil -} - func (dvd DefaultValueDecoders) floatDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { var f float64 var err error @@ -527,30 +442,6 @@ func (dvd DefaultValueDecoders) FloatDecodeValue(ec DecodeContext, vr ValueReade return nil } -// StringDecodeValue is the ValueDecoderFunc for string types. -// -// Deprecated: StringDecodeValue is not registered by default. Use StringCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) StringDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error { - var str string - var err error - switch vr.Type() { - // TODO(GODRIVER-577): Handle JavaScript and Symbol BSON types when allowed. - case TypeString: - str, err = vr.ReadString() - if err != nil { - return err - } - default: - return fmt.Errorf("cannot decode %v into a string type", vr.Type()) - } - if !val.CanSet() || val.Kind() != reflect.String { - return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val} - } - - val.SetString(str) - return nil -} - func (DefaultValueDecoders) javaScriptDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tJavaScript { return emptyValue, ValueDecoderError{ @@ -1287,114 +1178,6 @@ func (dvd DefaultValueDecoders) URLDecodeValue(dc DecodeContext, vr ValueReader, return nil } -// TimeDecodeValue is the ValueDecoderFunc for time.Time. -// -// Deprecated: TimeDecodeValue is not registered by default. Use TimeCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) TimeDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error { - if vr.Type() != TypeDateTime { - return fmt.Errorf("cannot decode %v into a time.Time", vr.Type()) - } - - dt, err := vr.ReadDateTime() - if err != nil { - return err - } - - if !val.CanSet() || val.Type() != tTime { - return ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: val} - } - - val.Set(reflect.ValueOf(time.Unix(dt/1000, dt%1000*1000000).UTC())) - return nil -} - -// ByteSliceDecodeValue is the ValueDecoderFunc for []byte. -// -// Deprecated: ByteSliceDecodeValue is not registered by default. Use ByteSliceCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) ByteSliceDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error { - if vr.Type() != TypeBinary && vr.Type() != TypeNull { - return fmt.Errorf("cannot decode %v into a []byte", vr.Type()) - } - - if !val.CanSet() || val.Type() != tByteSlice { - return ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: val} - } - - if vr.Type() == TypeNull { - val.Set(reflect.Zero(val.Type())) - return vr.ReadNull() - } - - data, subtype, err := vr.ReadBinary() - if err != nil { - return err - } - if subtype != 0x00 { - return fmt.Errorf("ByteSliceDecodeValue can only be used to decode subtype 0x00 for %s, got %v", TypeBinary, subtype) - } - - val.Set(reflect.ValueOf(data)) - return nil -} - -// MapDecodeValue is the ValueDecoderFunc for map[string]* types. -// -// Deprecated: MapDecodeValue is not registered by default. Use MapCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) MapDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { - if !val.CanSet() || val.Kind() != reflect.Map || val.Type().Key().Kind() != reflect.String { - return ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val} - } - - switch vr.Type() { - case Type(0), TypeEmbeddedDocument: - case TypeNull: - val.Set(reflect.Zero(val.Type())) - return vr.ReadNull() - default: - return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type()) - } - - dr, err := vr.ReadDocument() - if err != nil { - return err - } - - if val.IsNil() { - val.Set(reflect.MakeMap(val.Type())) - } - - eType := val.Type().Elem() - decoder, err := dc.LookupDecoder(eType) - if err != nil { - return err - } - - if eType == tEmpty { - dc.Ancestor = val.Type() - } - - keyType := val.Type().Key() - for { - key, vr, err := dr.ReadElement() - if errors.Is(err, ErrEOD) { - break - } - if err != nil { - return err - } - - elem := reflect.New(eType).Elem() - - err = decoder.DecodeValue(dc, vr, elem) - if err != nil { - return err - } - - val.SetMapIndex(reflect.ValueOf(key).Convert(keyType), elem) - } - return nil -} - // ArrayDecodeValue is the ValueDecoderFunc for array types. // // Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with all default @@ -1464,51 +1247,6 @@ func (dvd DefaultValueDecoders) ArrayDecodeValue(dc DecodeContext, vr ValueReade return nil } -// SliceDecodeValue is the ValueDecoderFunc for slice types. -// -// Deprecated: SliceDecodeValue is not registered by default. Use SliceCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) SliceDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { - if !val.CanSet() || val.Kind() != reflect.Slice { - return ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val} - } - - switch vr.Type() { - case TypeArray: - case TypeNull: - val.Set(reflect.Zero(val.Type())) - return vr.ReadNull() - case Type(0), TypeEmbeddedDocument: - if val.Type().Elem() != tE { - return fmt.Errorf("cannot decode document into %s", val.Type()) - } - default: - return fmt.Errorf("cannot decode %v into a slice", vr.Type()) - } - - var elemsFunc func(DecodeContext, ValueReader, reflect.Value) ([]reflect.Value, error) - switch val.Type().Elem() { - case tE: - dc.Ancestor = val.Type() - elemsFunc = dvd.decodeD - default: - elemsFunc = dvd.decodeDefault - } - - elems, err := elemsFunc(dc, vr, val) - if err != nil { - return err - } - - if val.IsNil() { - val.Set(reflect.MakeSlice(val.Type(), 0, len(elems))) - } - - val.SetLen(0) - val.Set(reflect.Append(val, elems...)) - - return nil -} - // ValueUnmarshalerDecodeValue is the ValueDecoderFunc for ValueUnmarshaler implementations. // // Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with all default @@ -1593,46 +1331,6 @@ func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(_ DecodeContext, vr Value return m.UnmarshalBSON(src) } -// EmptyInterfaceDecodeValue is the ValueDecoderFunc for interface{}. -// -// Deprecated: EmptyInterfaceDecodeValue is not registered by default. Use EmptyInterfaceCodec.DecodeValue instead. -func (dvd DefaultValueDecoders) EmptyInterfaceDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { - if !val.CanSet() || val.Type() != tEmpty { - return ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: val} - } - - rtype, err := dc.LookupTypeMapEntry(vr.Type()) - if err != nil { - switch vr.Type() { - case TypeEmbeddedDocument: - if dc.Ancestor != nil { - rtype = dc.Ancestor - break - } - rtype = tD - case TypeNull: - val.Set(reflect.Zero(val.Type())) - return vr.ReadNull() - default: - return err - } - } - - decoder, err := dc.LookupDecoder(rtype) - if err != nil { - return err - } - - elem := reflect.New(rtype).Elem() - err = decoder.DecodeValue(dc, vr, elem) - if err != nil { - return err - } - - val.Set(elem) - return nil -} - // CoreDocumentDecodeValue is the ValueDecoderFunc for bsoncore.Document. // // Deprecated: Use [go.mongodb.org/mongo-driver/bson.NewRegistry] to get a registry with all default @@ -1663,11 +1361,13 @@ func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr ValueReader, eType := val.Type().Elem() - decoder, err := dc.LookupDecoder(eType) - if err != nil { - return nil, err + var vDecoder ValueDecoder + if !(eType.Kind() == reflect.Interface && val.Len() > 0) { + vDecoder, err = dc.LookupDecoder(eType) + if err != nil { + return nil, err + } } - eTypeDecoder, _ := decoder.(typeDecoder) idx := 0 for { @@ -1679,10 +1379,41 @@ func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr ValueReader, return nil, err } - elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, eType, true) - if err != nil { - return nil, newDecodeError(strconv.Itoa(idx), err) + var elem reflect.Value + if vDecoder == nil { + elem = val.Index(idx).Elem() + if elem.Kind() != reflect.Ptr || elem.IsNil() { + valueDecoder, err := dc.LookupDecoder(elem.Type()) + if err != nil { + return nil, err + } + err = valueDecoder.DecodeValue(dc, vr, elem) + if err != nil { + return nil, newDecodeError(strconv.Itoa(idx), err) + } + } else if vr.Type() == TypeNull { + if err = vr.ReadNull(); err != nil { + return nil, err + } + elem = reflect.Zero(val.Index(idx).Type()) + } else { + e := elem.Elem() + valueDecoder, err := dc.LookupDecoder(e.Type()) + if err != nil { + return nil, err + } + err = valueDecoder.DecodeValue(dc, vr, e) + if err != nil { + return nil, newDecodeError(strconv.Itoa(idx), err) + } + } + } else { + elem, err = decodeTypeOrValueWithInfo(vDecoder, dc, vr, eType) + if err != nil { + return nil, newDecodeError(strconv.Itoa(idx), err) + } } + elems = append(elems, elem) idx++ } diff --git a/bson/default_value_decoders_test.go b/bson/default_value_decoders_test.go index 699a958605..31148ab644 100644 --- a/bson/default_value_decoders_test.go +++ b/bson/default_value_decoders_test.go @@ -2472,14 +2472,6 @@ func TestDefaultValueDecoders(t *testing.T) { } }) - t.Run("SliceCodec/DecodeValue/can't set slice", func(t *testing.T) { - var val []string - want := ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(val)} - got := dvd.SliceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val)) - if !assert.CompareErrors(got, want) { - t.Errorf("Errors do not match. got %v; want %v", got, want) - } - }) t.Run("SliceCodec/DecodeValue/too many elements", func(t *testing.T) { idx, doc := bsoncore.AppendDocumentStart(nil) aidx, doc := bsoncore.AppendArrayElementStart(doc, "foo") diff --git a/bson/empty_interface_codec.go b/bson/empty_interface_codec.go index 56468e3068..e0af34c942 100644 --- a/bson/empty_interface_codec.go +++ b/bson/empty_interface_codec.go @@ -125,7 +125,7 @@ func (eic EmptyInterfaceCodec) decodeType(dc DecodeContext, vr ValueReader, t re return emptyValue, err } - elem, err := decodeTypeOrValue(decoder, dc, vr, rtype) + elem, err := decodeTypeOrValueWithInfo(decoder, dc, vr, rtype) if err != nil { return emptyValue, err } diff --git a/bson/map_codec.go b/bson/map_codec.go index 9592957db4..fddcc5c8b7 100644 --- a/bson/map_codec.go +++ b/bson/map_codec.go @@ -189,7 +189,6 @@ func (mc *MapCodec) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Va if err != nil { return err } - eTypeDecoder, _ := decoder.(typeDecoder) if eType == tEmpty { dc.Ancestor = val.Type() @@ -211,7 +210,7 @@ func (mc *MapCodec) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Va return err } - elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, eType, true) + elem, err := decodeTypeOrValueWithInfo(decoder, dc, vr, eType) if err != nil { return newDecodeError(key, err) } diff --git a/bson/struct_codec.go b/bson/struct_codec.go index 917ac17bfd..14337c7a2e 100644 --- a/bson/struct_codec.go +++ b/bson/struct_codec.go @@ -349,6 +349,19 @@ func (sc *StructCodec) DecodeValue(dc DecodeContext, vr ValueReader, val reflect } } + if field.Kind() == reflect.Interface && !field.IsNil() && field.Elem().Kind() == reflect.Ptr { + v := field.Elem().Elem() + decoder, err = dc.LookupDecoder(v.Type()) + if err != nil { + return err + } + err = decoder.DecodeValue(dc, vr, v) + if err != nil { + return newDecodeError(fd.name, err) + } + continue + } + if !field.CanSet() { // Being settable is a super set of being addressable. innerErr := fmt.Errorf("field %v is not settable", field) return newDecodeError(fd.name, innerErr) diff --git a/bson/unmarshal_test.go b/bson/unmarshal_test.go index 0871237386..d83edd3940 100644 --- a/bson/unmarshal_test.go +++ b/bson/unmarshal_test.go @@ -412,6 +412,157 @@ func TestUnmarshalExtJSONWithUndefinedField(t *testing.T) { } } +func TestUnmarshalInterface(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + stub func() ([]byte, interface{}, func(*testing.T)) + } + testCases := []testCase{ + { + name: "struct with interface containing a concrete value", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Value interface{} + } + var value string + + data := docToBytes(struct { + Value string + }{ + Value: "foo", + }) + + receiver := testStruct{&value} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", value) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing a struct", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type demo struct { + Data string + } + + type testStruct struct { + Value interface{} + } + var value demo + + data := docToBytes(struct { + Value demo + }{ + Value: demo{"foo"}, + }) + + receiver := testStruct{&value} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", value.Data) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing a slice", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values interface{} + } + var values []string + + data := docToBytes(struct { + Values []string + }{ + Values: []string{"foo", "bar"}, + }) + + receiver := testStruct{&values} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, []string{"foo", "bar"}, values) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface containing an array", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values interface{} + } + var values [2]string + + data := docToBytes(struct { + Values []string + }{ + Values: []string{"foo", "bar"}, + }) + + receiver := testStruct{&values} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, [2]string{"foo", "bar"}, values) + } + + return data, &receiver, check + }, + }, + { + name: "struct with interface array containing concrete values", + stub: func() ([]byte, interface{}, func(*testing.T)) { + type testStruct struct { + Values [3]interface{} + } + var str string + var i, j int + + data := docToBytes(struct { + Values []interface{} + }{ + Values: []interface{}{"foo", 42, nil}, + }) + + receiver := testStruct{[3]interface{}{&str, &i, &j}} + + check := func(t *testing.T) { + t.Helper() + assert.Equal(t, "foo", str) + assert.Equal(t, 42, i) + assert.Equal(t, 0, j) + assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver) + } + + return data, &receiver, check + }, + }, + } + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + data, receiver, check := tc.stub() + err := Unmarshal(data, receiver) + noerr(t, err) + check(t) + }) + } +} + func TestUnmarshalBSONWithUndefinedField(t *testing.T) { // When unmarshalling BSON, fields that are undefined in the destination struct are skipped. // This process must not skip other, defined fields and must not raise errors.