encode.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. package dynamodbattribute
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strconv"
  6. "time"
  7. "github.com/aws/aws-sdk-go/service/dynamodb"
  8. )
  9. // A Marshaler is an interface to provide custom marshaling of Go value types
  10. // to AttributeValues. Use this to provide custom logic determining how a
  11. // Go Value type should be marshaled.
  12. //
  13. // type ExampleMarshaler struct {
  14. // Value int
  15. // }
  16. // type (m *ExampleMarshaler) MarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error {
  17. // n := fmt.Sprintf("%v", m.Value)
  18. // av.N = &n
  19. //
  20. // return nil
  21. // }
  22. //
  23. type Marshaler interface {
  24. MarshalDynamoDBAttributeValue(*dynamodb.AttributeValue) error
  25. }
  26. // Marshal will serialize the passed in Go value type into a DynamoDB AttributeValue
  27. // type. This value can be used in DynamoDB API operations to simplify marshaling
  28. // your Go value types into AttributeValues.
  29. //
  30. // Marshal will recursively transverse the passed in value marshaling its
  31. // contents into a AttributeValue. Marshal supports basic scalars
  32. // (int,uint,float,bool,string), maps, slices, and structs. Anonymous
  33. // nested types are flattened based on Go anonymous type visibility.
  34. //
  35. // Marshaling slices to AttributeValue will default to a List for all
  36. // types except for []byte and [][]byte. []byte will be marshaled as
  37. // Binary data (B), and [][]byte will be marshaled as binary data set
  38. // (BS).
  39. //
  40. // `dynamodbav` struct tag can be used to control how the value will be
  41. // marshaled into a AttributeValue.
  42. //
  43. // // Field is ignored
  44. // Field int `dynamodbav:"-"`
  45. //
  46. // // Field AttributeValue map key "myName"
  47. // Field int `dynamodbav:"myName"`
  48. //
  49. // // Field AttributeValue map key "myName", and
  50. // // Field is omitted if it is empty
  51. // Field int `dynamodbav:"myName,omitempty"`
  52. //
  53. // // Field AttributeValue map key "Field", and
  54. // // Field is omitted if it is empty
  55. // Field int `dynamodbav:",omitempty"`
  56. //
  57. // // Field's elems will be omitted if empty
  58. // // only valid for slices, and maps.
  59. // Field []string `dynamodbav:",omitemptyelem"`
  60. //
  61. // // Field will be marshaled as a AttributeValue string
  62. // // only value for number types, (int,uint,float)
  63. // Field int `dynamodbav:",string"`
  64. //
  65. // // Field will be marshaled as a binary set
  66. // Field [][]byte `dynamodbav:",binaryset"`
  67. //
  68. // // Field will be marshaled as a number set
  69. // Field []int `dynamodbav:",numberset"`
  70. //
  71. // // Field will be marshaled as a string set
  72. // Field []string `dynamodbav:",stringset"`
  73. //
  74. // The omitempty tag is only used during Marshaling and is ignored for
  75. // Unmarshal. Any zero value or a value when marshaled results in a
  76. // AttributeValue NULL will be added to AttributeValue Maps during struct
  77. // marshal. The omitemptyelem tag works the same as omitempty except it
  78. // applies to maps and slices instead of struct fields, and will not be
  79. // included in the marshaled AttributeValue Map, List, or Set.
  80. //
  81. // For convenience and backwards compatibility with ConvertTo functions
  82. // json struct tags are supported by the Marshal and Unmarshal. If
  83. // both json and dynamodbav struct tags are provided the json tag will
  84. // be ignored in favor of dynamodbav.
  85. //
  86. // All struct fields and with anonymous fields, are marshaled unless the
  87. // any of the following conditions are meet.
  88. //
  89. // - the field is not exported
  90. // - json or dynamodbav field tag is "-"
  91. // - json or dynamodbav field tag specifies "omitempty", and is empty.
  92. //
  93. // Pointer and interfaces values encode as the value pointed to or contained
  94. // in the interface. A nil value encodes as the AttributeValue NULL value.
  95. //
  96. // Channel, complex, and function values are not encoded and will be skipped
  97. // when walking the value to be marshaled.
  98. //
  99. // When marshaling any error that occurs will halt the marshal and return
  100. // the error.
  101. //
  102. // Marshal cannot represent cyclic data structures and will not handle them.
  103. // Passing cyclic structures to Marshal will result in an infinite recursion.
  104. func Marshal(in interface{}) (*dynamodb.AttributeValue, error) {
  105. return NewEncoder().Encode(in)
  106. }
  107. // MarshalMap is an alias for Marshal func which marshals Go value
  108. // type to a map of AttributeValues.
  109. func MarshalMap(in interface{}) (map[string]*dynamodb.AttributeValue, error) {
  110. av, err := NewEncoder().Encode(in)
  111. if err != nil || av == nil || av.M == nil {
  112. return map[string]*dynamodb.AttributeValue{}, err
  113. }
  114. return av.M, nil
  115. }
  116. // MarshalList is an alias for Marshal func which marshals Go value
  117. // type to a slice of AttributeValues.
  118. func MarshalList(in interface{}) ([]*dynamodb.AttributeValue, error) {
  119. av, err := NewEncoder().Encode(in)
  120. if err != nil || av == nil || av.L == nil {
  121. return []*dynamodb.AttributeValue{}, err
  122. }
  123. return av.L, nil
  124. }
  125. // A MarshalOptions is a collection of options shared between marshaling
  126. // and unmarshaling
  127. type MarshalOptions struct {
  128. // States that the encoding/json struct tags should be supported.
  129. // if a `dynamodbav` struct tag is also provided the encoding/json
  130. // tag will be ignored.
  131. //
  132. // Enabled by default.
  133. SupportJSONTags bool
  134. }
  135. // An Encoder provides marshaling Go value types to AttributeValues.
  136. type Encoder struct {
  137. MarshalOptions
  138. // Empty strings, "", will be marked as NULL AttributeValue types.
  139. // Empty strings are not valid values for DynamoDB. Will not apply
  140. // to lists, sets, or maps. Use the struct tag `omitemptyelem`
  141. // to skip empty (zero) values in lists, sets and maps.
  142. //
  143. // Enabled by default.
  144. NullEmptyString bool
  145. }
  146. // NewEncoder creates a new Encoder with default configuration. Use
  147. // the `opts` functional options to override the default configuration.
  148. func NewEncoder(opts ...func(*Encoder)) *Encoder {
  149. e := &Encoder{
  150. MarshalOptions: MarshalOptions{
  151. SupportJSONTags: true,
  152. },
  153. NullEmptyString: true,
  154. }
  155. for _, o := range opts {
  156. o(e)
  157. }
  158. return e
  159. }
  160. // Encode will marshal a Go value type to an AttributeValue. Returning
  161. // the AttributeValue constructed or error.
  162. func (e *Encoder) Encode(in interface{}) (*dynamodb.AttributeValue, error) {
  163. av := &dynamodb.AttributeValue{}
  164. if err := e.encode(av, reflect.ValueOf(in), tag{}); err != nil {
  165. return nil, err
  166. }
  167. return av, nil
  168. }
  169. func (e *Encoder) encode(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
  170. // We should check for omitted values first before dereferencing.
  171. if fieldTag.OmitEmpty && emptyValue(v) {
  172. encodeNull(av)
  173. return nil
  174. }
  175. // Handle both pointers and interface conversion into types
  176. v = valueElem(v)
  177. if v.Kind() != reflect.Invalid {
  178. if used, err := tryMarshaler(av, v); used {
  179. return err
  180. }
  181. }
  182. switch v.Kind() {
  183. case reflect.Invalid:
  184. encodeNull(av)
  185. case reflect.Struct:
  186. return e.encodeStruct(av, v)
  187. case reflect.Map:
  188. return e.encodeMap(av, v, fieldTag)
  189. case reflect.Slice, reflect.Array:
  190. return e.encodeSlice(av, v, fieldTag)
  191. case reflect.Chan, reflect.Func, reflect.UnsafePointer:
  192. // do nothing for unsupported types
  193. default:
  194. return e.encodeScalar(av, v, fieldTag)
  195. }
  196. return nil
  197. }
  198. func (e *Encoder) encodeStruct(av *dynamodb.AttributeValue, v reflect.Value) error {
  199. // To maintain backwards compatibility with ConvertTo family of methods which
  200. // converted time.Time structs to strings
  201. if t, ok := v.Interface().(time.Time); ok {
  202. s := t.Format(time.RFC3339Nano)
  203. av.S = &s
  204. return nil
  205. }
  206. av.M = map[string]*dynamodb.AttributeValue{}
  207. fields := unionStructFields(v.Type(), e.MarshalOptions)
  208. for _, f := range fields {
  209. if f.Name == "" {
  210. return &InvalidMarshalError{msg: "map key cannot be empty"}
  211. }
  212. fv := v.FieldByIndex(f.Index)
  213. elem := &dynamodb.AttributeValue{}
  214. err := e.encode(elem, fv, f.tag)
  215. if err != nil {
  216. return err
  217. }
  218. skip, err := keepOrOmitEmpty(f.OmitEmpty, elem, err)
  219. if err != nil {
  220. return err
  221. } else if skip {
  222. continue
  223. }
  224. av.M[f.Name] = elem
  225. }
  226. if len(av.M) == 0 {
  227. encodeNull(av)
  228. }
  229. return nil
  230. }
  231. func (e *Encoder) encodeMap(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
  232. av.M = map[string]*dynamodb.AttributeValue{}
  233. for _, key := range v.MapKeys() {
  234. keyName := fmt.Sprint(key.Interface())
  235. if keyName == "" {
  236. return &InvalidMarshalError{msg: "map key cannot be empty"}
  237. }
  238. elemVal := v.MapIndex(key)
  239. elem := &dynamodb.AttributeValue{}
  240. err := e.encode(elem, elemVal, tag{})
  241. skip, err := keepOrOmitEmpty(fieldTag.OmitEmptyElem, elem, err)
  242. if err != nil {
  243. return err
  244. } else if skip {
  245. continue
  246. }
  247. av.M[keyName] = elem
  248. }
  249. if len(av.M) == 0 {
  250. encodeNull(av)
  251. }
  252. return nil
  253. }
  254. func (e *Encoder) encodeSlice(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
  255. switch v.Type().Elem().Kind() {
  256. case reflect.Uint8:
  257. b := v.Bytes()
  258. if len(b) == 0 {
  259. encodeNull(av)
  260. return nil
  261. }
  262. av.B = append([]byte{}, b...)
  263. default:
  264. var elemFn func(dynamodb.AttributeValue) error
  265. if fieldTag.AsBinSet || v.Type() == byteSliceSlicetype { // Binary Set
  266. av.BS = make([][]byte, 0, v.Len())
  267. elemFn = func(elem dynamodb.AttributeValue) error {
  268. if elem.B == nil {
  269. return &InvalidMarshalError{msg: "binary set must only contain non-nil byte slices"}
  270. }
  271. av.BS = append(av.BS, elem.B)
  272. return nil
  273. }
  274. } else if fieldTag.AsNumSet { // Number Set
  275. av.NS = make([]*string, 0, v.Len())
  276. elemFn = func(elem dynamodb.AttributeValue) error {
  277. if elem.N == nil {
  278. return &InvalidMarshalError{msg: "number set must only contain non-nil string numbers"}
  279. }
  280. av.NS = append(av.NS, elem.N)
  281. return nil
  282. }
  283. } else if fieldTag.AsStrSet { // String Set
  284. av.SS = make([]*string, 0, v.Len())
  285. elemFn = func(elem dynamodb.AttributeValue) error {
  286. if elem.S == nil {
  287. return &InvalidMarshalError{msg: "string set must only contain non-nil strings"}
  288. }
  289. av.SS = append(av.SS, elem.S)
  290. return nil
  291. }
  292. } else { // List
  293. av.L = make([]*dynamodb.AttributeValue, 0, v.Len())
  294. elemFn = func(elem dynamodb.AttributeValue) error {
  295. av.L = append(av.L, &elem)
  296. return nil
  297. }
  298. }
  299. if n, err := e.encodeList(v, fieldTag, elemFn); err != nil {
  300. return err
  301. } else if n == 0 {
  302. encodeNull(av)
  303. }
  304. }
  305. return nil
  306. }
  307. func (e *Encoder) encodeList(v reflect.Value, fieldTag tag, elemFn func(dynamodb.AttributeValue) error) (int, error) {
  308. count := 0
  309. for i := 0; i < v.Len(); i++ {
  310. elem := dynamodb.AttributeValue{}
  311. err := e.encode(&elem, v.Index(i), tag{OmitEmpty: fieldTag.OmitEmptyElem})
  312. skip, err := keepOrOmitEmpty(fieldTag.OmitEmptyElem, &elem, err)
  313. if err != nil {
  314. return 0, err
  315. } else if skip {
  316. continue
  317. }
  318. if err := elemFn(elem); err != nil {
  319. return 0, err
  320. }
  321. count++
  322. }
  323. return count, nil
  324. }
  325. func (e *Encoder) encodeScalar(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
  326. if v.Type() == numberType {
  327. s := v.String()
  328. if fieldTag.AsString {
  329. av.S = &s
  330. } else {
  331. av.N = &s
  332. }
  333. return nil
  334. }
  335. switch v.Kind() {
  336. case reflect.Bool:
  337. av.BOOL = new(bool)
  338. *av.BOOL = v.Bool()
  339. case reflect.String:
  340. if err := e.encodeString(av, v); err != nil {
  341. return err
  342. }
  343. default:
  344. // Fallback to encoding numbers, will return invalid type if not supported
  345. if err := e.encodeNumber(av, v); err != nil {
  346. return err
  347. }
  348. if fieldTag.AsString && av.NULL == nil && av.N != nil {
  349. av.S = av.N
  350. av.N = nil
  351. }
  352. }
  353. return nil
  354. }
  355. func (e *Encoder) encodeNumber(av *dynamodb.AttributeValue, v reflect.Value) error {
  356. if used, err := tryMarshaler(av, v); used {
  357. return err
  358. }
  359. var out string
  360. switch v.Kind() {
  361. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  362. out = encodeInt(v.Int())
  363. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  364. out = encodeUint(v.Uint())
  365. case reflect.Float32, reflect.Float64:
  366. out = encodeFloat(v.Float())
  367. default:
  368. return &unsupportedMarshalTypeError{Type: v.Type()}
  369. }
  370. av.N = &out
  371. return nil
  372. }
  373. func (e *Encoder) encodeString(av *dynamodb.AttributeValue, v reflect.Value) error {
  374. if used, err := tryMarshaler(av, v); used {
  375. return err
  376. }
  377. switch v.Kind() {
  378. case reflect.String:
  379. s := v.String()
  380. if len(s) == 0 && e.NullEmptyString {
  381. encodeNull(av)
  382. } else {
  383. av.S = &s
  384. }
  385. default:
  386. return &unsupportedMarshalTypeError{Type: v.Type()}
  387. }
  388. return nil
  389. }
  390. func encodeInt(i int64) string {
  391. return strconv.FormatInt(i, 10)
  392. }
  393. func encodeUint(u uint64) string {
  394. return strconv.FormatUint(u, 10)
  395. }
  396. func encodeFloat(f float64) string {
  397. return strconv.FormatFloat(f, 'f', -1, 64)
  398. }
  399. func encodeNull(av *dynamodb.AttributeValue) {
  400. t := true
  401. *av = dynamodb.AttributeValue{NULL: &t}
  402. }
  403. func valueElem(v reflect.Value) reflect.Value {
  404. switch v.Kind() {
  405. case reflect.Interface, reflect.Ptr:
  406. for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
  407. v = v.Elem()
  408. }
  409. }
  410. return v
  411. }
  412. func emptyValue(v reflect.Value) bool {
  413. switch v.Kind() {
  414. case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
  415. return v.Len() == 0
  416. case reflect.Bool:
  417. return !v.Bool()
  418. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  419. return v.Int() == 0
  420. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  421. return v.Uint() == 0
  422. case reflect.Float32, reflect.Float64:
  423. return v.Float() == 0
  424. case reflect.Interface, reflect.Ptr:
  425. return v.IsNil()
  426. }
  427. return false
  428. }
  429. func tryMarshaler(av *dynamodb.AttributeValue, v reflect.Value) (bool, error) {
  430. if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
  431. v = v.Addr()
  432. }
  433. if v.Type().NumMethod() == 0 {
  434. return false, nil
  435. }
  436. if m, ok := v.Interface().(Marshaler); ok {
  437. return true, m.MarshalDynamoDBAttributeValue(av)
  438. }
  439. return false, nil
  440. }
  441. func keepOrOmitEmpty(omitEmpty bool, av *dynamodb.AttributeValue, err error) (bool, error) {
  442. if err != nil {
  443. if _, ok := err.(*unsupportedMarshalTypeError); ok {
  444. return true, nil
  445. }
  446. return false, err
  447. }
  448. if av.NULL != nil && omitEmpty {
  449. return true, nil
  450. }
  451. return false, nil
  452. }
  453. // An InvalidMarshalError is an error type representing an error
  454. // occurring when marshaling a Go value type to an AttributeValue.
  455. type InvalidMarshalError struct {
  456. emptyOrigError
  457. msg string
  458. }
  459. // Error returns the string representation of the error.
  460. // satisfying the error interface
  461. func (e *InvalidMarshalError) Error() string {
  462. return fmt.Sprintf("%s: %s", e.Code(), e.Message())
  463. }
  464. // Code returns the code of the error, satisfying the awserr.Error
  465. // interface.
  466. func (e *InvalidMarshalError) Code() string {
  467. return "InvalidMarshalError"
  468. }
  469. // Message returns the detailed message of the error, satisfying
  470. // the awserr.Error interface.
  471. func (e *InvalidMarshalError) Message() string {
  472. return e.msg
  473. }
  474. // An unsupportedMarshalTypeError represents a Go value type
  475. // which cannot be marshaled into an AttributeValue and should
  476. // be skipped by the marshaler.
  477. type unsupportedMarshalTypeError struct {
  478. emptyOrigError
  479. Type reflect.Type
  480. }
  481. // Error returns the string representation of the error.
  482. // satisfying the error interface
  483. func (e *unsupportedMarshalTypeError) Error() string {
  484. return fmt.Sprintf("%s: %s", e.Code(), e.Message())
  485. }
  486. // Code returns the code of the error, satisfying the awserr.Error
  487. // interface.
  488. func (e *unsupportedMarshalTypeError) Code() string {
  489. return "unsupportedMarshalTypeError"
  490. }
  491. // Message returns the detailed message of the error, satisfying
  492. // the awserr.Error interface.
  493. func (e *unsupportedMarshalTypeError) Message() string {
  494. return "Go value type " + e.Type.String() + " is not supported"
  495. }