encode.go 18 KB

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