Skip to content

Commit

Permalink
Add FixUnmarshalIndividualSetValues option to DecoderOptions of dynamodb
Browse files Browse the repository at this point in the history
  • Loading branch information
AnatolyRugalev committed Jan 22, 2025
1 parent e5689c9 commit e1ee8da
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 6 deletions.
10 changes: 10 additions & 0 deletions .changelog/50cedb35-ef4d-49a5-bd20-4882e48ddf70.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "50cedb35-ef4d-49a5-bd20-4882e48ddf70",
"type": "feature",
"collapse": false,
"description": "Add FixUnmarshalIndividualSetValues option to DecoderOptions to fix unmarshalling of individual values of StringSet, NumberSet, and BinarySet attributes.",
"modules": [
"feature/dynamodb/attributevalue",
"feature/dynamodbstreams/attributevalue"
]
}
35 changes: 32 additions & 3 deletions feature/dynamodb/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ type DecoderOptions struct {
// call UnmarshalText on the target. If the attributevalue is a binary, its
// value will be used to call UnmarshalBinary.
UseEncodingUnmarshalers bool

// When enabled, the decoder will call Unmarshaler.UnmarshalDynamoDBAttributeValue
// for each individual set item instead of the whole set at once.
// See issue #2895.
FixUnmarshalIndividualSetValues bool
}

// A Decoder provides unmarshaling AttributeValues to Go value types.
Expand Down Expand Up @@ -447,7 +452,15 @@ func (d *Decoder) decodeBinarySet(bs [][]byte, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberBS{Value: bs})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberB{Value: bs[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberBS{Value: bs})
}
}
if err := d.decodeBinary(bs[i], elem); err != nil {
return err
Expand Down Expand Up @@ -582,7 +595,15 @@ func (d *Decoder) decodeNumberSet(ns []string, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberNS{Value: ns})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberN{Value: ns[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberNS{Value: ns})
}
}
if err := d.decodeNumber(ns[i], elem, tag{}); err != nil {
return err
Expand Down Expand Up @@ -804,7 +825,15 @@ func (d *Decoder) decodeStringSet(ss []string, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberSS{Value: ss})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberS{Value: ss[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBAttributeValue(&types.AttributeValueMemberSS{Value: ss})
}
}
if err := d.decodeString(ss[i], elem, tag{}); err != nil {
return err
Expand Down
76 changes: 76 additions & 0 deletions feature/dynamodb/attributevalue/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1268,3 +1268,79 @@ func TestUnmarshalBinary(t *testing.T) {
t.Errorf("expected %v, got %v", expected, actual)
}
}

type testStringItem string

func (t *testStringItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberS)
if !ok {
return fmt.Errorf("expecting string value")
}
*t = testStringItem(v.Value)
return nil
}

type testNumberItem float64

func (t *testNumberItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberN)
if !ok {
return fmt.Errorf("expecting number value")
}
n, err := strconv.ParseFloat(v.Value, 64)
if err != nil {
return err
}
*t = testNumberItem(n)
return nil
}

type testBinaryItem []byte

func (t *testBinaryItem) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberB)
if !ok {
return fmt.Errorf("expecting binary value")
}
*t = make([]byte, len(v.Value))
copy(*t, v.Value)
return nil
}

type testStringSetWithUnmarshaler struct {
Strings []testStringItem `dynamodbav:",stringset"`
Numbers []testNumberItem `dynamodbav:",numberset"`
Binaries []testBinaryItem `dynamodbav:",binaryset"`
}

func TestUnmarshalIndividualSetValues(t *testing.T) {
in := &types.AttributeValueMemberM{
Value: map[string]types.AttributeValue{
"Strings": &types.AttributeValueMemberSS{
Value: []string{"a", "b"},
},
"Numbers": &types.AttributeValueMemberNS{
Value: []string{"1", "2"},
},
"Binaries": &types.AttributeValueMemberBS{
Value: [][]byte{{1, 2}, {3, 4}},
},
},
}
var actual testStringSetWithUnmarshaler
err := UnmarshalWithOptions(in, &actual, func(o *DecoderOptions) {
o.FixUnmarshalIndividualSetValues = true
})
if err != nil {
t.Fatalf("expect no error, got %v", err)
}

expected := testStringSetWithUnmarshaler{
Strings: []testStringItem{"a", "b"},
Numbers: []testNumberItem{1, 2},
Binaries: []testBinaryItem{{1, 2}, {3, 4}},
}
if diff := cmpDiff(expected, actual); diff != "" {
t.Errorf("expect value match\n%s", diff)
}
}
35 changes: 32 additions & 3 deletions feature/dynamodbstreams/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ type DecoderOptions struct {
// call UnmarshalText on the target. If the attributevalue is a binary, its
// value will be used to call UnmarshalBinary.
UseEncodingUnmarshalers bool

// When enabled, the decoder will call Unmarshaler.UnmarshalDynamoDBStreamsAttributeValue
// for each individual set item instead of the whole set at once.
// See issue #2895.
FixUnmarshalIndividualSetValues bool
}

// A Decoder provides unmarshaling AttributeValues to Go value types.
Expand Down Expand Up @@ -447,7 +452,15 @@ func (d *Decoder) decodeBinarySet(bs [][]byte, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberBS{Value: bs})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberB{Value: bs[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberBS{Value: bs})
}
}
if err := d.decodeBinary(bs[i], elem); err != nil {
return err
Expand Down Expand Up @@ -582,7 +595,15 @@ func (d *Decoder) decodeNumberSet(ns []string, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberNS{Value: ns})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberN{Value: ns[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberNS{Value: ns})
}
}
if err := d.decodeNumber(ns[i], elem, tag{}); err != nil {
return err
Expand Down Expand Up @@ -804,7 +825,15 @@ func (d *Decoder) decodeStringSet(ss []string, v reflect.Value) error {
}
u, elem := indirect[Unmarshaler](v.Index(i), indirectOptions{})
if u != nil {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberSS{Value: ss})
if d.options.FixUnmarshalIndividualSetValues {
err := u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberS{Value: ss[i]})
if err != nil {
return err
}
continue
} else {
return u.UnmarshalDynamoDBStreamsAttributeValue(&types.AttributeValueMemberSS{Value: ss})
}
}
if err := d.decodeString(ss[i], elem, tag{}); err != nil {
return err
Expand Down
76 changes: 76 additions & 0 deletions feature/dynamodbstreams/attributevalue/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1268,3 +1268,79 @@ func TestUnmarshalBinary(t *testing.T) {
t.Errorf("expected %v, got %v", expected, actual)
}
}

type testStringItem string

func (t *testStringItem) UnmarshalDynamoDBStreasmAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberS)
if !ok {
return fmt.Errorf("expecting string value")
}
*t = testStringItem(v.Value)
return nil
}

type testNumberItem float64

func (t *testNumberItem) UnmarshalDynamoDBStreamsAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberN)
if !ok {
return fmt.Errorf("expecting number value")
}
n, err := strconv.ParseFloat(v.Value, 64)
if err != nil {
return err
}
*t = testNumberItem(n)
return nil
}

type testBinaryItem []byte

func (t *testBinaryItem) UnmarshalDynamoDBStreamsAttributeValue(av types.AttributeValue) error {
v, ok := av.(*types.AttributeValueMemberB)
if !ok {
return fmt.Errorf("expecting binary value")
}
*t = make([]byte, len(v.Value))
copy(*t, v.Value)
return nil
}

type testStringSetWithUnmarshaler struct {
Strings []testStringItem `dynamodbav:",stringset"`
Numbers []testNumberItem `dynamodbav:",numberset"`
Binaries []testBinaryItem `dynamodbav:",binaryset"`
}

func TestUnmarshalIndividualSetValues(t *testing.T) {
in := &types.AttributeValueMemberM{
Value: map[string]types.AttributeValue{
"Strings": &types.AttributeValueMemberSS{
Value: []string{"a", "b"},
},
"Numbers": &types.AttributeValueMemberNS{
Value: []string{"1", "2"},
},
"Binaries": &types.AttributeValueMemberBS{
Value: [][]byte{{1, 2}, {3, 4}},
},
},
}
var actual testStringSetWithUnmarshaler
err := UnmarshalWithOptions(in, &actual, func(o *DecoderOptions) {
o.FixUnmarshalIndividualSetValues = true
})
if err != nil {
t.Fatalf("expect no error, got %v", err)
}

expected := testStringSetWithUnmarshaler{
Strings: []testStringItem{"a", "b"},
Numbers: []testNumberItem{1, 2},
Binaries: []testBinaryItem{{1, 2}, {3, 4}},
}
if diff := cmpDiff(expected, actual); diff != "" {
t.Errorf("expect value match\n%s", diff)
}
}

0 comments on commit e1ee8da

Please sign in to comment.