Skip to content

Commit

Permalink
i1494 passing opts to attributevalue marshalling (#1495)
Browse files Browse the repository at this point in the history
Fixes the SDK's code generation to pin smithy-cli to $smithyVersion to
restrict the supported version.

Co-authored-by: Jason Del Ponte <961963+jasdel@users.noreply.github.com>
  • Loading branch information
radutopala and jasdel authored Jan 11, 2022
1 parent 576b415 commit 07e5d3e
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .changelog/5ab32814eb5e456ea921d0f0a645ca5b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "5ab32814-eb5e-456e-a921-d0f0a645ca5b",
"type": "feature",
"description": "Adds new MarshalWithOptions and UnmarshalWithOptions helpers allowing Encoding and Decoding options to be specified when serializing AttributeValues. Addresses issue: https://github.com/aws/aws-sdk-go-v2/issues/1494",
"modules": [
"feature/dynamodb/attributevalue"
]
}
1 change: 1 addition & 0 deletions codegen/protocol-test-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ plugins {
}

dependencies {
implementation("software.amazon.smithy:smithy-cli:$smithyVersion")
implementation("software.amazon.smithy:smithy-aws-protocol-tests:$smithyVersion")
implementation(project(":smithy-aws-go-codegen"))
}
Expand Down
85 changes: 85 additions & 0 deletions feature/dynamodb/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,53 @@ func Unmarshal(av types.AttributeValue, out interface{}) error {
return NewDecoder().Decode(av, out)
}

// UnmarshalWithOptions will unmarshal AttributeValues to Go value types.
// Both generic interface{} and concrete types are valid unmarshal
// destination types.
//
// Use the `optsFns` functional options to override the default configuration.
//
// UnmarshalWithOptions will allocate maps, slices, and pointers as needed to
// unmarshal the AttributeValue into the provided type value.
//
// When unmarshaling AttributeValues into structs Unmarshal matches
// the field names of the struct to the AttributeValue Map keys.
// Initially it will look for exact field name matching, but will
// fall back to case insensitive if not exact match is found.
//
// With the exception of omitempty, omitemptyelem, binaryset, numberset
// and stringset all struct tags used by Marshal are also used by
// UnmarshalWithOptions.
//
// When decoding AttributeValues to interfaces Unmarshal will use the
// following types.
//
// []byte, AV Binary (B)
// [][]byte, AV Binary Set (BS)
// bool, AV Boolean (BOOL)
// []interface{}, AV List (L)
// map[string]interface{}, AV Map (M)
// float64, AV Number (N)
// Number, AV Number (N) with UseNumber set
// []float64, AV Number Set (NS)
// []Number, AV Number Set (NS) with UseNumber set
// string, AV String (S)
// []string, AV String Set (SS)
//
// If the Decoder option, UseNumber is set numbers will be unmarshaled
// as Number values instead of float64. Use this to maintain the original
// string formating of the number as it was represented in the AttributeValue.
// In addition provides additional opportunities to parse the number
// string based on individual use cases.
//
// When unmarshaling any error that occurs will halt the unmarshal
// and return the error.
//
// The output value provided must be a non-nil pointer
func UnmarshalWithOptions(av types.AttributeValue, out interface{}, optFns ...func(options *DecoderOptions)) error {
return NewDecoder(optFns...).Decode(av, out)
}

// UnmarshalMap is an alias for Unmarshal which unmarshals from
// a map of AttributeValues.
//
Expand All @@ -87,6 +134,16 @@ func UnmarshalMap(m map[string]types.AttributeValue, out interface{}) error {
return NewDecoder().Decode(&types.AttributeValueMemberM{Value: m}, out)
}

// UnmarshalMapWithOptions is an alias for UnmarshalWithOptions which unmarshals from
// a map of AttributeValues.
//
// Use the `optsFns` functional options to override the default configuration.
//
// The output value provided must be a non-nil pointer
func UnmarshalMapWithOptions(m map[string]types.AttributeValue, out interface{}, optFns ...func(options *DecoderOptions)) error {
return NewDecoder(optFns...).Decode(&types.AttributeValueMemberM{Value: m}, out)
}

// UnmarshalList is an alias for Unmarshal func which unmarshals
// a slice of AttributeValues.
//
Expand All @@ -95,6 +152,16 @@ func UnmarshalList(l []types.AttributeValue, out interface{}) error {
return NewDecoder().Decode(&types.AttributeValueMemberL{Value: l}, out)
}

// UnmarshalListWithOptions is an alias for UnmarshalWithOptions func which unmarshals
// a slice of AttributeValues.
//
// Use the `optsFns` functional options to override the default configuration.
//
// The output value provided must be a non-nil pointer
func UnmarshalListWithOptions(l []types.AttributeValue, out interface{}, optFns ...func(options *DecoderOptions)) error {
return NewDecoder(optFns...).Decode(&types.AttributeValueMemberL{Value: l}, out)
}

// UnmarshalListOfMaps is an alias for Unmarshal func which unmarshals a
// slice of maps of attribute values.
//
Expand All @@ -111,6 +178,24 @@ func UnmarshalListOfMaps(l []map[string]types.AttributeValue, out interface{}) e
return UnmarshalList(items, out)
}

// UnmarshalListOfMapsWithOptions is an alias for UnmarshalWithOptions func which unmarshals a
// slice of maps of attribute values.
//
// Use the `optsFns` functional options to override the default configuration.
//
// This is useful for when you need to unmarshal the Items from a Query API
// call.
//
// The output value provided must be a non-nil pointer
func UnmarshalListOfMapsWithOptions(l []map[string]types.AttributeValue, out interface{}, optFns ...func(options *DecoderOptions)) error {
items := make([]types.AttributeValue, len(l))
for i, m := range l {
items[i] = &types.AttributeValueMemberM{Value: m}
}

return UnmarshalListWithOptions(items, out, optFns...)
}

// DecoderOptions is a collection of options to configure how the decoder
// unmarshalls the value.
type DecoderOptions struct {
Expand Down
139 changes: 139 additions & 0 deletions feature/dynamodb/attributevalue/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,115 @@ func Marshal(in interface{}) (types.AttributeValue, error) {
return NewEncoder().Encode(in)
}

// MarshalWithOptions will serialize the passed in Go value type into a AttributeValue
// type, by using . This value can be used in API operations to simplify marshaling
// your Go value types into AttributeValues.
//
// Use the `optsFns` functional options to override the default configuration.
//
// MarshalWithOptions will recursively transverse the passed in value marshaling its
// contents into a AttributeValue. Marshal supports basic scalars
// (int,uint,float,bool,string), maps, slices, and structs. Anonymous
// nested types are flattened based on Go anonymous type visibility.
//
// Marshaling slices to AttributeValue will default to a List for all
// types except for []byte and [][]byte. []byte will be marshaled as
// Binary data (B), and [][]byte will be marshaled as binary data set
// (BS).
//
// The `time.Time` type is marshaled as `time.RFC3339Nano` format.
//
// `dynamodbav` struct tag can be used to control how the value will be
// marshaled into a AttributeValue.
//
// // Field is ignored
// Field int `dynamodbav:"-"`
//
// // Field AttributeValue map key "myName"
// Field int `dynamodbav:"myName"`
//
// // Field AttributeValue map key "myName", and
// // Field is omitted if the field is a zero value for the type.
// Field int `dynamodbav:"myName,omitempty"`
//
// // Field AttributeValue map key "Field", and
// // Field is omitted if the field is a zero value for the type.
// Field int `dynamodbav:",omitempty"`
//
// // Field's elems will be omitted if the elem's value is empty.
// // only valid for slices, and maps.
// Field []string `dynamodbav:",omitemptyelem"`
//
// // Field AttributeValue map key "Field", and
// // Field is sent as NULL if the field is a zero value for the type.
// Field int `dynamodbav:",nullempty"`
//
// // Field's elems will be sent as NULL if the elem's value a zero value
// // for the type. Only valid for slices, and maps.
// Field []string `dynamodbav:",nullemptyelem"`
//
// // Field will be marshaled as a AttributeValue string
// // only value for number types, (int,uint,float)
// Field int `dynamodbav:",string"`
//
// // Field will be marshaled as a binary set
// Field [][]byte `dynamodbav:",binaryset"`
//
// // Field will be marshaled as a number set
// Field []int `dynamodbav:",numberset"`
//
// // Field will be marshaled as a string set
// Field []string `dynamodbav:",stringset"`
//
// // Field will be marshaled as Unix time number in seconds.
// // This tag is only valid with time.Time typed struct fields.
// // Important to note that zero value time as unixtime is not 0 seconds
// // from January 1, 1970 UTC, but -62135596800. Which is seconds between
// // January 1, 0001 UTC, and January 1, 0001 UTC.
// Field time.Time `dynamodbav:",unixtime"`
//
// The omitempty tag is only used during Marshaling and is ignored for
// Unmarshal. omitempty will skip any member if the Go value of the member is
// zero. The omitemptyelem tag works the same as omitempty except it applies to
// the elements of maps and slices instead of struct fields, and will not be
// included in the marshaled AttributeValue Map, List, or Set.
//
// The nullempty tag is only used during Marshaling and is ignored for
// Unmarshal. nullempty will serialize a AttributeValueMemberNULL for the
// member if the Go value of the member is zero. nullemptyelem tag works the
// same as nullempty except it applies to the elements of maps and slices
// instead of struct fields, and will not be included in the marshaled
// AttributeValue Map, List, or Set.
//
// All struct fields and with anonymous fields, are marshaled unless the
// any of the following conditions are meet.
//
// - the field is not exported
// - json or dynamodbav field tag is "-"
// - json or dynamodbav field tag specifies "omitempty", and is a zero value.
//
// Pointer and interfaces values are encoded as the value pointed to or
// contained in the interface. A nil value encodes as the AttributeValue NULL
// value unless `omitempty` struct tag is provided.
//
// Channel, complex, and function values are not encoded and will be skipped
// when walking the value to be marshaled.
//
// Error that occurs when marshaling will stop the marshal, and return
// the error.
//
// MarshalWithOptions cannot represent cyclic data structures and will not handle them.
// Passing cyclic structures to Marshal will result in an infinite recursion.
func MarshalWithOptions(in interface{}, optFns ...func(*EncoderOptions)) (types.AttributeValue, error) {
return NewEncoder(optFns...).Encode(in)
}

// MarshalMap is an alias for Marshal func which marshals Go value type to a
// map of AttributeValues. If the in parameter does not serialize to a map, an
// empty AttributeValue map will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
//
// This is useful for APIs such as PutItem.
func MarshalMap(in interface{}) (map[string]types.AttributeValue, error) {
av, err := NewEncoder().Encode(in)
Expand All @@ -195,6 +300,24 @@ func MarshalMap(in interface{}) (map[string]types.AttributeValue, error) {
return asMap.Value, nil
}

// MarshalMapWithOptions is an alias for MarshalWithOptions func which marshals Go value type to a
// map of AttributeValues. If the in parameter does not serialize to a map, an
// empty AttributeValue map will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
//
// This is useful for APIs such as PutItem.
func MarshalMapWithOptions(in interface{}, optFns ...func(*EncoderOptions)) (map[string]types.AttributeValue, error) {
av, err := NewEncoder(optFns...).Encode(in)

asMap, ok := av.(*types.AttributeValueMemberM)
if err != nil || av == nil || !ok {
return map[string]types.AttributeValue{}, err
}

return asMap.Value, nil
}

// MarshalList is an alias for Marshal func which marshals Go value
// type to a slice of AttributeValues. If the in parameter does not serialize
// to a slice, an empty AttributeValue slice will be returned.
Expand All @@ -209,6 +332,22 @@ func MarshalList(in interface{}) ([]types.AttributeValue, error) {
return asList.Value, nil
}

// MarshalListWithOptions is an alias for MarshalWithOptions func which marshals Go value
// type to a slice of AttributeValues. If the in parameter does not serialize
// to a slice, an empty AttributeValue slice will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
func MarshalListWithOptions(in interface{}, optFns ...func(*EncoderOptions)) ([]types.AttributeValue, error) {
av, err := NewEncoder(optFns...).Encode(in)

asList, ok := av.(*types.AttributeValueMemberL)
if err != nil || av == nil || !ok {
return []types.AttributeValue{}, err
}

return asList.Value, nil
}

// EncoderOptions is a collection of options shared between marshaling
// and unmarshaling
type EncoderOptions struct {
Expand Down

0 comments on commit 07e5d3e

Please sign in to comment.