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

fix(dynamodb): fix StringSet, NumberSet and BinarySet decoding #2896

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
34 changes: 31 additions & 3 deletions feature/dynamodb/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ 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.
UnmarshalIndividualSetValues bool
AnatolyRugalev marked this conversation as resolved.
Show resolved Hide resolved
}

// A Decoder provides unmarshaling AttributeValues to Go value types.
Expand Down Expand Up @@ -447,7 +451,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.UnmarshalIndividualSetValues {
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 +594,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.UnmarshalIndividualSetValues {
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 +824,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.UnmarshalIndividualSetValues {
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.UnmarshalIndividualSetValues = 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)
}
}
34 changes: 31 additions & 3 deletions feature/dynamodbstreams/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ 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.
UnmarshalIndividualSetValues bool
}

// A Decoder provides unmarshaling AttributeValues to Go value types.
Expand Down Expand Up @@ -447,7 +451,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.UnmarshalIndividualSetValues {
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 +594,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.UnmarshalIndividualSetValues {
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 +824,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.UnmarshalIndividualSetValues {
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.UnmarshalIndividualSetValues = 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)
}
}