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