Skip to content

Commit d5773a9

Browse files
Add FixUnmarshalIndividualSetValues option to DecoderOptions of dynamodb (#2896)
1 parent 58e23dc commit d5773a9

File tree

5 files changed

+226
-6
lines changed

5 files changed

+226
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"id": "50cedb35-ef4d-49a5-bd20-4882e48ddf70",
3+
"type": "feature",
4+
"collapse": false,
5+
"description": "Add FixUnmarshalIndividualSetValues option to DecoderOptions to fix unmarshalling of individual values of StringSet, NumberSet, and BinarySet attributes.",
6+
"modules": [
7+
"feature/dynamodb/attributevalue",
8+
"feature/dynamodbstreams/attributevalue"
9+
]
10+
}

feature/dynamodb/attributevalue/decode.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ type DecoderOptions struct {
243243
// call UnmarshalText on the target. If the attributevalue is a binary, its
244244
// value will be used to call UnmarshalBinary.
245245
UseEncodingUnmarshalers bool
246+
247+
// When enabled, the decoder will call Unmarshaler.UnmarshalDynamoDBAttributeValue
248+
// for each individual set item instead of the whole set at once.
249+
// See issue #2895.
250+
FixUnmarshalIndividualSetValues bool
246251
}
247252

248253
// A Decoder provides unmarshaling AttributeValues to Go value types.
@@ -447,7 +452,15 @@ func (d *Decoder) decodeBinarySet(bs [][]byte, v reflect.Value) error {
447452
}
448453
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
449454
if u != nil {
450-
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberBS{Value: bs})
455+
if d.options.FixUnmarshalIndividualSetValues {
456+
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberB{Value: bs[i]})
457+
if err != nil {
458+
return err
459+
}
460+
continue
461+
} else {
462+
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberBS{Value: bs})
463+
}
451464
}
452465
if err := d.decodeBinary(bs[i], elem); err != nil {
453466
return err
@@ -582,7 +595,15 @@ func (d *Decoder) decodeNumberSet(ns []string, v reflect.Value) error {
582595
}
583596
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
584597
if u != nil {
585-
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberNS{Value: ns})
598+
if d.options.FixUnmarshalIndividualSetValues {
599+
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberN{Value: ns[i]})
600+
if err != nil {
601+
return err
602+
}
603+
continue
604+
} else {
605+
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberNS{Value: ns})
606+
}
586607
}
587608
if err := d.decodeNumber(ns[i], elem, tag{}); err != nil {
588609
return err
@@ -804,7 +825,15 @@ func (d *Decoder) decodeStringSet(ss []string, v reflect.Value) error {
804825
}
805826
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
806827
if u != nil {
807-
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberSS{Value: ss})
828+
if d.options.FixUnmarshalIndividualSetValues {
829+
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberS{Value: ss[i]})
830+
if err != nil {
831+
return err
832+
}
833+
continue
834+
} else {
835+
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberSS{Value: ss})
836+
}
808837
}
809838
if err := d.decodeString(ss[i], elem, tag{}); err != nil {
810839
return err

feature/dynamodb/attributevalue/decode_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -1268,3 +1268,79 @@ func TestUnmarshalBinary(t *testing.T) {
12681268
t.Errorf("expected %v, got %v", expected, actual)
12691269
}
12701270
}
1271+
1272+
type testStringItem string
1273+
1274+
func (t *testStringItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
1275+
v, ok := av.(*types.AttributeValueMemberS)
1276+
if !ok {
1277+
return fmt.Errorf("expecting string value")
1278+
}
1279+
*t = testStringItem(v.Value)
1280+
return nil
1281+
}
1282+
1283+
type testNumberItem float64
1284+
1285+
func (t *testNumberItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
1286+
v, ok := av.(*types.AttributeValueMemberN)
1287+
if !ok {
1288+
return fmt.Errorf("expecting number value")
1289+
}
1290+
n, err := strconv.ParseFloat(v.Value, 64)
1291+
if err != nil {
1292+
return err
1293+
}
1294+
*t = testNumberItem(n)
1295+
return nil
1296+
}
1297+
1298+
type testBinaryItem []byte
1299+
1300+
func (t *testBinaryItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
1301+
v, ok := av.(*types.AttributeValueMemberB)
1302+
if !ok {
1303+
return fmt.Errorf("expecting binary value")
1304+
}
1305+
*t = make([]byte, len(v.Value))
1306+
copy(*t, v.Value)
1307+
return nil
1308+
}
1309+
1310+
type testStringSetWithUnmarshaler struct {
1311+
Strings []testStringItem `dynamodbav:",stringset"`
1312+
Numbers []testNumberItem `dynamodbav:",numberset"`
1313+
Binaries []testBinaryItem `dynamodbav:",binaryset"`
1314+
}
1315+
1316+
func TestUnmarshalIndividualSetValues(t *testing.T) {
1317+
in := &types.AttributeValueMemberM{
1318+
Value: map[string]types.AttributeValue{
1319+
"Strings": &types.AttributeValueMemberSS{
1320+
Value: []string{"a", "b"},
1321+
},
1322+
"Numbers": &types.AttributeValueMemberNS{
1323+
Value: []string{"1", "2"},
1324+
},
1325+
"Binaries": &types.AttributeValueMemberBS{
1326+
Value: [][]byte{{1, 2}, {3, 4}},
1327+
},
1328+
},
1329+
}
1330+
var actual testStringSetWithUnmarshaler
1331+
err := UnmarshalWithOptions(in, &actual, func(o *DecoderOptions) {
1332+
o.FixUnmarshalIndividualSetValues = true
1333+
})
1334+
if err != nil {
1335+
t.Fatalf("expect no error, got %v", err)
1336+
}
1337+
1338+
expected := testStringSetWithUnmarshaler{
1339+
Strings: []testStringItem{"a", "b"},
1340+
Numbers: []testNumberItem{1, 2},
1341+
Binaries: []testBinaryItem{{1, 2}, {3, 4}},
1342+
}
1343+
if diff := cmpDiff(expected, actual); diff != "" {
1344+
t.Errorf("expect value match\n%s", diff)
1345+
}
1346+
}

feature/dynamodbstreams/attributevalue/decode.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ type DecoderOptions struct {
243243
// call UnmarshalText on the target. If the attributevalue is a binary, its
244244
// value will be used to call UnmarshalBinary.
245245
UseEncodingUnmarshalers bool
246+
247+
// When enabled, the decoder will call Unmarshaler.UnmarshalDynamoDBStreamsAttributeValue
248+
// for each individual set item instead of the whole set at once.
249+
// See issue #2895.
250+
FixUnmarshalIndividualSetValues bool
246251
}
247252

248253
// A Decoder provides unmarshaling AttributeValues to Go value types.
@@ -447,7 +452,15 @@ func (d *Decoder) decodeBinarySet(bs [][]byte, v reflect.Value) error {
447452
}
448453
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
449454
if u != nil {
450-
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberBS{Value: bs})
455+
if d.options.FixUnmarshalIndividualSetValues {
456+
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberB{Value: bs[i]})
457+
if err != nil {
458+
return err
459+
}
460+
continue
461+
} else {
462+
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberBS{Value: bs})
463+
}
451464
}
452465
if err := d.decodeBinary(bs[i], elem); err != nil {
453466
return err
@@ -582,7 +595,15 @@ func (d *Decoder) decodeNumberSet(ns []string, v reflect.Value) error {
582595
}
583596
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
584597
if u != nil {
585-
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberNS{Value: ns})
598+
if d.options.FixUnmarshalIndividualSetValues {
599+
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberN{Value: ns[i]})
600+
if err != nil {
601+
return err
602+
}
603+
continue
604+
} else {
605+
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberNS{Value: ns})
606+
}
586607
}
587608
if err := d.decodeNumber(ns[i], elem, tag{}); err != nil {
588609
return err
@@ -804,7 +825,15 @@ func (d *Decoder) decodeStringSet(ss []string, v reflect.Value) error {
804825
}
805826
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
806827
if u != nil {
807-
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberSS{Value: ss})
828+
if d.options.FixUnmarshalIndividualSetValues {
829+
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberS{Value: ss[i]})
830+
if err != nil {
831+
return err
832+
}
833+
continue
834+
} else {
835+
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberSS{Value: ss})
836+
}
808837
}
809838
if err := d.decodeString(ss[i], elem, tag{}); err != nil {
810839
return err

feature/dynamodbstreams/attributevalue/decode_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -1268,3 +1268,79 @@ func TestUnmarshalBinary(t *testing.T) {
12681268
t.Errorf("expected %v, got %v", expected, actual)
12691269
}
12701270
}
1271+
1272+
type testStringItem string
1273+
1274+
func (t *testStringItem) UnmarshalDynamoDBStreasmAttributeValue(av types.AttributeValue) error {
1275+
v, ok := av.(*types.AttributeValueMemberS)
1276+
if !ok {
1277+
return fmt.Errorf("expecting string value")
1278+
}
1279+
*t = testStringItem(v.Value)
1280+
return nil
1281+
}
1282+
1283+
type testNumberItem float64
1284+
1285+
func (t *testNumberItem) UnmarshalDynamoDBStreamsAttributeValue(av types.AttributeValue) error {
1286+
v, ok := av.(*types.AttributeValueMemberN)
1287+
if !ok {
1288+
return fmt.Errorf("expecting number value")
1289+
}
1290+
n, err := strconv.ParseFloat(v.Value, 64)
1291+
if err != nil {
1292+
return err
1293+
}
1294+
*t = testNumberItem(n)
1295+
return nil
1296+
}
1297+
1298+
type testBinaryItem []byte
1299+
1300+
func (t *testBinaryItem) UnmarshalDynamoDBStreamsAttributeValue(av types.AttributeValue) error {
1301+
v, ok := av.(*types.AttributeValueMemberB)
1302+
if !ok {
1303+
return fmt.Errorf("expecting binary value")
1304+
}
1305+
*t = make([]byte, len(v.Value))
1306+
copy(*t, v.Value)
1307+
return nil
1308+
}
1309+
1310+
type testStringSetWithUnmarshaler struct {
1311+
Strings []testStringItem `dynamodbav:",stringset"`
1312+
Numbers []testNumberItem `dynamodbav:",numberset"`
1313+
Binaries []testBinaryItem `dynamodbav:",binaryset"`
1314+
}
1315+
1316+
func TestUnmarshalIndividualSetValues(t *testing.T) {
1317+
in := &types.AttributeValueMemberM{
1318+
Value: map[string]types.AttributeValue{
1319+
"Strings": &types.AttributeValueMemberSS{
1320+
Value: []string{"a", "b"},
1321+
},
1322+
"Numbers": &types.AttributeValueMemberNS{
1323+
Value: []string{"1", "2"},
1324+
},
1325+
"Binaries": &types.AttributeValueMemberBS{
1326+
Value: [][]byte{{1, 2}, {3, 4}},
1327+
},
1328+
},
1329+
}
1330+
var actual testStringSetWithUnmarshaler
1331+
err := UnmarshalWithOptions(in, &actual, func(o *DecoderOptions) {
1332+
o.FixUnmarshalIndividualSetValues = true
1333+
})
1334+
if err != nil {
1335+
t.Fatalf("expect no error, got %v", err)
1336+
}
1337+
1338+
expected := testStringSetWithUnmarshaler{
1339+
Strings: []testStringItem{"a", "b"},
1340+
Numbers: []testNumberItem{1, 2},
1341+
Binaries: []testBinaryItem{{1, 2}, {3, 4}},
1342+
}
1343+
if diff := cmpDiff(expected, actual); diff != "" {
1344+
t.Errorf("expect value match\n%s", diff)
1345+
}
1346+
}

0 commit comments

Comments
 (0)