| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- package dynamodbattribute
- import (
- "fmt"
- "reflect"
- "strconv"
- "time"
- "github.com/aws/aws-sdk-go/service/dynamodb"
- )
- // A Marshaler is an interface to provide custom marshaling of Go value types
- // to AttributeValues. Use this to provide custom logic determining how a
- // Go Value type should be marshaled.
- //
- // type ExampleMarshaler struct {
- // Value int
- // }
- // type (m *ExampleMarshaler) MarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error {
- // n := fmt.Sprintf("%v", m.Value)
- // av.N = &n
- //
- // return nil
- // }
- //
- type Marshaler interface {
- MarshalDynamoDBAttributeValue(*dynamodb.AttributeValue) error
- }
- // Marshal will serialize the passed in Go value type into a DynamoDB AttributeValue
- // type. This value can be used in DynamoDB API operations to simplify marshaling
- // your Go value types into AttributeValues.
- //
- // Marshal 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).
- //
- // `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 it is empty
- // Field int `dynamodbav:"myName,omitempty"`
- //
- // // Field AttributeValue map key "Field", and
- // // Field is omitted if it is empty
- // Field int `dynamodbav:",omitempty"`
- //
- // // Field's elems will be omitted if empty
- // // only valid for slices, and maps.
- // Field []string `dynamodbav:",omitemptyelem"`
- //
- // // 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"`
- //
- // The omitempty tag is only used during Marshaling and is ignored for
- // Unmarshal. Any zero value or a value when marshaled results in a
- // AttributeValue NULL will be added to AttributeValue Maps during struct
- // marshal. The omitemptyelem tag works the same as omitempty except it
- // applies to maps and slices instead of struct fields, and will not be
- // included in the marshaled AttributeValue Map, List, or Set.
- //
- // For convenience and backwards compatibility with ConvertTo functions
- // json struct tags are supported by the Marshal and Unmarshal. If
- // both json and dynamodbav struct tags are provided the json tag will
- // be ignored in favor of dynamodbav.
- //
- // 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 empty.
- //
- // Pointer and interfaces values encode as the value pointed to or contained
- // in the interface. A nil value encodes as the AttributeValue NULL value.
- //
- // Channel, complex, and function values are not encoded and will be skipped
- // when walking the value to be marshaled.
- //
- // When marshaling any error that occurs will halt the marshal and return
- // the error.
- //
- // Marshal cannot represent cyclic data structures and will not handle them.
- // Passing cyclic structures to Marshal will result in an infinite recursion.
- func Marshal(in interface{}) (*dynamodb.AttributeValue, error) {
- return NewEncoder().Encode(in)
- }
- // MarshalMap is an alias for Marshal func which marshals Go value
- // type to a map of AttributeValues.
- func MarshalMap(in interface{}) (map[string]*dynamodb.AttributeValue, error) {
- av, err := NewEncoder().Encode(in)
- if err != nil || av == nil || av.M == nil {
- return map[string]*dynamodb.AttributeValue{}, err
- }
- return av.M, nil
- }
- // MarshalList is an alias for Marshal func which marshals Go value
- // type to a slice of AttributeValues.
- func MarshalList(in interface{}) ([]*dynamodb.AttributeValue, error) {
- av, err := NewEncoder().Encode(in)
- if err != nil || av == nil || av.L == nil {
- return []*dynamodb.AttributeValue{}, err
- }
- return av.L, nil
- }
- // A MarshalOptions is a collection of options shared between marshaling
- // and unmarshaling
- type MarshalOptions struct {
- // States that the encoding/json struct tags should be supported.
- // if a `dynamodbav` struct tag is also provided the encoding/json
- // tag will be ignored.
- //
- // Enabled by default.
- SupportJSONTags bool
- }
- // An Encoder provides marshaling Go value types to AttributeValues.
- type Encoder struct {
- MarshalOptions
- // Empty strings, "", will be marked as NULL AttributeValue types.
- // Empty strings are not valid values for DynamoDB. Will not apply
- // to lists, sets, or maps. Use the struct tag `omitemptyelem`
- // to skip empty (zero) values in lists, sets and maps.
- //
- // Enabled by default.
- NullEmptyString bool
- }
- // NewEncoder creates a new Encoder with default configuration. Use
- // the `opts` functional options to override the default configuration.
- func NewEncoder(opts ...func(*Encoder)) *Encoder {
- e := &Encoder{
- MarshalOptions: MarshalOptions{
- SupportJSONTags: true,
- },
- NullEmptyString: true,
- }
- for _, o := range opts {
- o(e)
- }
- return e
- }
- // Encode will marshal a Go value type to an AttributeValue. Returning
- // the AttributeValue constructed or error.
- func (e *Encoder) Encode(in interface{}) (*dynamodb.AttributeValue, error) {
- av := &dynamodb.AttributeValue{}
- if err := e.encode(av, reflect.ValueOf(in), tag{}); err != nil {
- return nil, err
- }
- return av, nil
- }
- func (e *Encoder) encode(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
- // We should check for omitted values first before dereferencing.
- if fieldTag.OmitEmpty && emptyValue(v) {
- encodeNull(av)
- return nil
- }
- // Handle both pointers and interface conversion into types
- v = valueElem(v)
- if v.Kind() != reflect.Invalid {
- if used, err := tryMarshaler(av, v); used {
- return err
- }
- }
- switch v.Kind() {
- case reflect.Invalid:
- encodeNull(av)
- case reflect.Struct:
- return e.encodeStruct(av, v)
- case reflect.Map:
- return e.encodeMap(av, v, fieldTag)
- case reflect.Slice, reflect.Array:
- return e.encodeSlice(av, v, fieldTag)
- case reflect.Chan, reflect.Func, reflect.UnsafePointer:
- // do nothing for unsupported types
- default:
- return e.encodeScalar(av, v, fieldTag)
- }
- return nil
- }
- func (e *Encoder) encodeStruct(av *dynamodb.AttributeValue, v reflect.Value) error {
- // To maintain backwards compatibility with ConvertTo family of methods which
- // converted time.Time structs to strings
- if t, ok := v.Interface().(time.Time); ok {
- s := t.Format(time.RFC3339Nano)
- av.S = &s
- return nil
- }
- av.M = map[string]*dynamodb.AttributeValue{}
- fields := unionStructFields(v.Type(), e.MarshalOptions)
- for _, f := range fields {
- if f.Name == "" {
- return &InvalidMarshalError{msg: "map key cannot be empty"}
- }
- fv := v.FieldByIndex(f.Index)
- elem := &dynamodb.AttributeValue{}
- err := e.encode(elem, fv, f.tag)
- if err != nil {
- return err
- }
- skip, err := keepOrOmitEmpty(f.OmitEmpty, elem, err)
- if err != nil {
- return err
- } else if skip {
- continue
- }
- av.M[f.Name] = elem
- }
- if len(av.M) == 0 {
- encodeNull(av)
- }
- return nil
- }
- func (e *Encoder) encodeMap(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
- av.M = map[string]*dynamodb.AttributeValue{}
- for _, key := range v.MapKeys() {
- keyName := fmt.Sprint(key.Interface())
- if keyName == "" {
- return &InvalidMarshalError{msg: "map key cannot be empty"}
- }
- elemVal := v.MapIndex(key)
- elem := &dynamodb.AttributeValue{}
- err := e.encode(elem, elemVal, tag{})
- skip, err := keepOrOmitEmpty(fieldTag.OmitEmptyElem, elem, err)
- if err != nil {
- return err
- } else if skip {
- continue
- }
- av.M[keyName] = elem
- }
- if len(av.M) == 0 {
- encodeNull(av)
- }
- return nil
- }
- func (e *Encoder) encodeSlice(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
- switch v.Type().Elem().Kind() {
- case reflect.Uint8:
- b := v.Bytes()
- if len(b) == 0 {
- encodeNull(av)
- return nil
- }
- av.B = append([]byte{}, b...)
- default:
- var elemFn func(dynamodb.AttributeValue) error
- if fieldTag.AsBinSet || v.Type() == byteSliceSlicetype { // Binary Set
- av.BS = make([][]byte, 0, v.Len())
- elemFn = func(elem dynamodb.AttributeValue) error {
- if elem.B == nil {
- return &InvalidMarshalError{msg: "binary set must only contain non-nil byte slices"}
- }
- av.BS = append(av.BS, elem.B)
- return nil
- }
- } else if fieldTag.AsNumSet { // Number Set
- av.NS = make([]*string, 0, v.Len())
- elemFn = func(elem dynamodb.AttributeValue) error {
- if elem.N == nil {
- return &InvalidMarshalError{msg: "number set must only contain non-nil string numbers"}
- }
- av.NS = append(av.NS, elem.N)
- return nil
- }
- } else if fieldTag.AsStrSet { // String Set
- av.SS = make([]*string, 0, v.Len())
- elemFn = func(elem dynamodb.AttributeValue) error {
- if elem.S == nil {
- return &InvalidMarshalError{msg: "string set must only contain non-nil strings"}
- }
- av.SS = append(av.SS, elem.S)
- return nil
- }
- } else { // List
- av.L = make([]*dynamodb.AttributeValue, 0, v.Len())
- elemFn = func(elem dynamodb.AttributeValue) error {
- av.L = append(av.L, &elem)
- return nil
- }
- }
- if n, err := e.encodeList(v, fieldTag, elemFn); err != nil {
- return err
- } else if n == 0 {
- encodeNull(av)
- }
- }
- return nil
- }
- func (e *Encoder) encodeList(v reflect.Value, fieldTag tag, elemFn func(dynamodb.AttributeValue) error) (int, error) {
- count := 0
- for i := 0; i < v.Len(); i++ {
- elem := dynamodb.AttributeValue{}
- err := e.encode(&elem, v.Index(i), tag{OmitEmpty: fieldTag.OmitEmptyElem})
- skip, err := keepOrOmitEmpty(fieldTag.OmitEmptyElem, &elem, err)
- if err != nil {
- return 0, err
- } else if skip {
- continue
- }
- if err := elemFn(elem); err != nil {
- return 0, err
- }
- count++
- }
- return count, nil
- }
- func (e *Encoder) encodeScalar(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
- if v.Type() == numberType {
- s := v.String()
- if fieldTag.AsString {
- av.S = &s
- } else {
- av.N = &s
- }
- return nil
- }
- switch v.Kind() {
- case reflect.Bool:
- av.BOOL = new(bool)
- *av.BOOL = v.Bool()
- case reflect.String:
- if err := e.encodeString(av, v); err != nil {
- return err
- }
- default:
- // Fallback to encoding numbers, will return invalid type if not supported
- if err := e.encodeNumber(av, v); err != nil {
- return err
- }
- if fieldTag.AsString && av.NULL == nil && av.N != nil {
- av.S = av.N
- av.N = nil
- }
- }
- return nil
- }
- func (e *Encoder) encodeNumber(av *dynamodb.AttributeValue, v reflect.Value) error {
- if used, err := tryMarshaler(av, v); used {
- return err
- }
- var out string
- switch v.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- out = encodeInt(v.Int())
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- out = encodeUint(v.Uint())
- case reflect.Float32, reflect.Float64:
- out = encodeFloat(v.Float())
- default:
- return &unsupportedMarshalTypeError{Type: v.Type()}
- }
- av.N = &out
- return nil
- }
- func (e *Encoder) encodeString(av *dynamodb.AttributeValue, v reflect.Value) error {
- if used, err := tryMarshaler(av, v); used {
- return err
- }
- switch v.Kind() {
- case reflect.String:
- s := v.String()
- if len(s) == 0 && e.NullEmptyString {
- encodeNull(av)
- } else {
- av.S = &s
- }
- default:
- return &unsupportedMarshalTypeError{Type: v.Type()}
- }
- return nil
- }
- func encodeInt(i int64) string {
- return strconv.FormatInt(i, 10)
- }
- func encodeUint(u uint64) string {
- return strconv.FormatUint(u, 10)
- }
- func encodeFloat(f float64) string {
- return strconv.FormatFloat(f, 'f', -1, 64)
- }
- func encodeNull(av *dynamodb.AttributeValue) {
- t := true
- *av = dynamodb.AttributeValue{NULL: &t}
- }
- func valueElem(v reflect.Value) reflect.Value {
- switch v.Kind() {
- case reflect.Interface, reflect.Ptr:
- for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
- v = v.Elem()
- }
- }
- return v
- }
- func emptyValue(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- }
- return false
- }
- func tryMarshaler(av *dynamodb.AttributeValue, v reflect.Value) (bool, error) {
- if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
- v = v.Addr()
- }
- if v.Type().NumMethod() == 0 {
- return false, nil
- }
- if m, ok := v.Interface().(Marshaler); ok {
- return true, m.MarshalDynamoDBAttributeValue(av)
- }
- return false, nil
- }
- func keepOrOmitEmpty(omitEmpty bool, av *dynamodb.AttributeValue, err error) (bool, error) {
- if err != nil {
- if _, ok := err.(*unsupportedMarshalTypeError); ok {
- return true, nil
- }
- return false, err
- }
- if av.NULL != nil && omitEmpty {
- return true, nil
- }
- return false, nil
- }
- // An InvalidMarshalError is an error type representing an error
- // occurring when marshaling a Go value type to an AttributeValue.
- type InvalidMarshalError struct {
- emptyOrigError
- msg string
- }
- // Error returns the string representation of the error.
- // satisfying the error interface
- func (e *InvalidMarshalError) Error() string {
- return fmt.Sprintf("%s: %s", e.Code(), e.Message())
- }
- // Code returns the code of the error, satisfying the awserr.Error
- // interface.
- func (e *InvalidMarshalError) Code() string {
- return "InvalidMarshalError"
- }
- // Message returns the detailed message of the error, satisfying
- // the awserr.Error interface.
- func (e *InvalidMarshalError) Message() string {
- return e.msg
- }
- // An unsupportedMarshalTypeError represents a Go value type
- // which cannot be marshaled into an AttributeValue and should
- // be skipped by the marshaler.
- type unsupportedMarshalTypeError struct {
- emptyOrigError
- Type reflect.Type
- }
- // Error returns the string representation of the error.
- // satisfying the error interface
- func (e *unsupportedMarshalTypeError) Error() string {
- return fmt.Sprintf("%s: %s", e.Code(), e.Message())
- }
- // Code returns the code of the error, satisfying the awserr.Error
- // interface.
- func (e *unsupportedMarshalTypeError) Code() string {
- return "unsupportedMarshalTypeError"
- }
- // Message returns the detailed message of the error, satisfying
- // the awserr.Error interface.
- func (e *unsupportedMarshalTypeError) Message() string {
- return "Go value type " + e.Type.String() + " is not supported"
- }
|