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

i1494 passing opts to attributevalue marshalling #1495

Merged
merged 3 commits into from Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changelog/5ab32814eb5e456ea921d0f0a645ca5b.json
@@ -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
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
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
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