github.com/smugmug/godynamo@v0.0.0-20151122084750-7913028f6623/types/attributevalue/attributevalue.go (about) 1 // Support for AttributeValue type. See 2 // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html 3 package attributevalue 4 5 import ( 6 "encoding/base64" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "github.com/smugmug/godynamo/types/cast" 11 "strconv" 12 ) 13 14 // SetList represents the SS,BS and NS types which are ostensibly sets but encoded as 15 // json lists. Duplicates are allowed but removed when marshaling or unmarshaling. 16 type SetList []string 17 18 // MarshalJSON will remove duplicates 19 func (s SetList) MarshalJSON() ([]byte, error) { 20 m := make(map[string]bool) 21 for _, v := range s { 22 m[v] = true 23 } 24 t := make([]string, len(m)) 25 i := 0 26 for k := range m { 27 t[i] = k 28 i++ 29 } 30 return json.Marshal(t) 31 } 32 33 // UnmarshalJSON will remove duplicates 34 func (s *SetList) UnmarshalJSON(data []byte) error { 35 if s == nil { 36 return errors.New("AttributeValue.UnmarshalJSON: pointer receiver for unmarshal nil") 37 } 38 t := make([]string, 0) 39 um_err := json.Unmarshal(data, &t) 40 if um_err != nil { 41 return um_err 42 } 43 m := make(map[string]bool) 44 for _, v := range t { 45 m[v] = true 46 } 47 for k := range m { 48 *s = append(*s, k) 49 } 50 return nil 51 } 52 53 type AttributeValue struct { 54 N string `json:",omitempty"` 55 S string `json:",omitempty"` 56 B string `json:",omitempty"` 57 58 // These are pointers so we can have a vacuous type (nil), otherwise, we don't know 59 // if false was a set value or the default. To set these 60 BOOL *bool `json:",omitempty"` 61 NULL *bool `json:",omitempty"` 62 63 L []*AttributeValue `json:",omitempty"` 64 M map[string]*AttributeValue `json:",omitempty"` 65 66 SS SetList `json:",omitempty"` 67 NS SetList `json:",omitempty"` 68 BS SetList `json:",omitempty"` 69 } 70 71 // Empty determines if an AttributeValue is vacuous. Explicitly do not bother 72 // testing the boolean fields. 73 func (a AttributeValue) Empty() bool { 74 return a.N == "" && 75 a.S == "" && 76 a.B == "" && 77 len(a.M) == 0 && 78 len(a.L) == 0 && 79 len(a.SS) == 0 && 80 len(a.NS) == 0 && 81 len(a.BS) == 0 && 82 a.BOOL == nil && 83 a.NULL == nil 84 } 85 86 type attributevalue AttributeValue 87 88 // MarshalJSON will emit null if the AttributeValue is Empty 89 func (a AttributeValue) MarshalJSON() ([]byte, error) { 90 if a.Empty() { 91 return json.Marshal(nil) 92 } else { 93 return json.Marshal(attributevalue(a)) 94 } 95 } 96 97 // Valid determines if more than one field has been set (in which case it is invalid). 98 func (a *AttributeValue) Valid() bool { 99 if a == nil { 100 return false 101 } 102 c := 0 103 if a.S != "" { 104 c++ 105 if c > 1 { 106 return false 107 } 108 } 109 if a.N != "" { 110 c++ 111 if c > 1 { 112 return false 113 } 114 } 115 if a.B != "" { 116 c++ 117 if c > 1 { 118 return false 119 } 120 } 121 if len(a.M) != 0 { 122 c++ 123 if c > 1 { 124 return false 125 } 126 } 127 if len(a.L) != 0 { 128 c++ 129 if c > 1 { 130 return false 131 } 132 } 133 if len(a.SS) != 0 { 134 c++ 135 if c > 1 { 136 return false 137 } 138 } 139 if len(a.NS) != 0 { 140 c++ 141 if c > 1 { 142 return false 143 } 144 } 145 if len(a.BS) != 0 { 146 c++ 147 if c > 1 { 148 return false 149 } 150 } 151 if a.BOOL != nil { 152 c++ 153 if c > 1 { 154 return false 155 } 156 } 157 if a.NULL != nil { 158 c++ 159 if c > 1 { 160 return false 161 } 162 } 163 return c == 1 164 } 165 166 func NewAttributeValue() *AttributeValue { 167 a := new(AttributeValue) 168 a.L = make([]*AttributeValue, 0) 169 a.M = make(map[string]*AttributeValue) 170 a.SS = make([]string, 0) 171 a.NS = make([]string, 0) 172 a.BS = make([]string, 0) 173 174 // BOOL and NULL let to nil to represent vacuous state 175 176 return a 177 } 178 179 // Copy makes a copy of the this AttributeValue into ac. 180 func (a *AttributeValue) Copy(ac *AttributeValue) error { 181 if a == nil { 182 return errors.New("AttributeValue.Copy: pointer receiver is nil") 183 } 184 if ac == nil { 185 return errors.New("AttributeValue.Copy: copy target attributevalue instance is nil") 186 } 187 ac.S = a.S 188 ac.N = a.N 189 ac.B = a.B 190 191 if a.BOOL == nil { 192 ac.BOOL = nil 193 } else { 194 ac.BOOL = new(bool) 195 *ac.BOOL = *a.BOOL 196 } 197 198 if a.NULL == nil { 199 ac.NULL = nil 200 } else { 201 ac.NULL = new(bool) 202 *ac.NULL = *a.NULL 203 } 204 205 l_ss := len(a.SS) 206 if l_ss != 0 { 207 ac.SS = make([]string, l_ss) 208 copy(ac.SS, a.SS) 209 } 210 211 l_ns := len(a.NS) 212 if l_ns != 0 { 213 ac.NS = make([]string, l_ns) 214 copy(ac.NS, a.NS) 215 } 216 217 l_bs := len(a.BS) 218 if l_bs != 0 { 219 ac.BS = make([]string, l_bs) 220 copy(ac.BS, a.BS) 221 } 222 223 // L is a recursive type, so the copy must be recursive 224 l_L := len(a.L) 225 if l_L != 0 { 226 ac.L = make([]*AttributeValue, l_L) 227 for i := range a.L { 228 ac.L[i] = NewAttributeValue() 229 L_i_cp_err := a.L[i].Copy(ac.L[i]) 230 if L_i_cp_err != nil { 231 return L_i_cp_err 232 } 233 } 234 } 235 236 // M is a recursive type, so the copy must be recursive 237 l_M := len(a.M) 238 if l_M != 0 { 239 ac.M = make(map[string]*AttributeValue, l_M) 240 for k := range a.M { 241 ac.M[k] = NewAttributeValue() 242 M_k_cp_err := a.M[k].Copy(ac.M[k]) 243 if M_k_cp_err != nil { 244 return M_k_cp_err 245 } 246 } 247 } 248 return nil 249 } 250 251 // InsertS sets the S field to string k 252 func (a *AttributeValue) InsertS(k string) error { 253 if a == nil { 254 return errors.New("AttributeValue.InsertS: pointer receiver is nil") 255 } 256 a.S = k 257 return nil 258 } 259 260 // InsertN sets the N field to number string k 261 func (a *AttributeValue) InsertN(k string) error { 262 if a == nil { 263 return errors.New("AttributeValue.InsertN: pointer receiver is nil") 264 } 265 fs, ferr := cast.AWSParseFloat(k) 266 if ferr != nil { 267 return ferr 268 } 269 a.N = fs 270 return nil 271 } 272 273 // InsertN_float64 works like InsertN but takes a float64 274 func (a *AttributeValue) InsertN_float64(f float64) error { 275 if a == nil { 276 return errors.New("AttributeValue.InsertN_float64: pointer receiver is nil") 277 } 278 a.N = strconv.FormatFloat(f, 'f', -1, 64) 279 return nil 280 } 281 282 // InsertB sets the B field to string k, which it is assumed the caller has 283 // already encoded. 284 func (a *AttributeValue) InsertB(k string) error { 285 if a == nil { 286 return errors.New("AttributeValue.InsertB: pointer receiver is nil") 287 } 288 berr := cast.AWSParseBinary(k) 289 if berr != nil { 290 return berr 291 } 292 a.B = k 293 return nil 294 } 295 296 // InsertB_unencoded adds a new plain string to the B field. 297 // The argument is assumed to be plaintext and will be base64 encoded. 298 func (a *AttributeValue) InsertB_unencoded(k string) error { 299 if a == nil { 300 return errors.New("AttributeValue.Insert_unencoded: pointer receiver is nil") 301 } 302 a.B = base64.StdEncoding.EncodeToString([]byte(k)) 303 return nil 304 } 305 306 // InsertSS adds a new string to the ss (JSON: SS) set. 307 // SS is *generated* from an internal representation (UM_ss) 308 // as it transforms a map into a list (a "set") 309 func (a *AttributeValue) InsertSS(k string) error { 310 if a == nil { 311 return errors.New("AttributeValue.InsertSS: pointer receiver is nil") 312 } 313 for _, v := range a.SS { 314 if v == k { 315 return nil 316 } 317 } 318 a.SS = append(a.SS, k) 319 return nil 320 } 321 322 // InsertNS adds a new number string to the ns (JSON: NS) set. 323 // String is parsed to make sure it is a represents a valid float. 324 // NS is *generated* from an internal representation (UM_ns) 325 // as it transforms a map into a list (a "set") 326 func (a *AttributeValue) InsertNS(k string) error { 327 if a == nil { 328 return errors.New("AttributeValue.InsertNS: pointer receiver is nil") 329 } 330 fs, ferr := cast.AWSParseFloat(k) 331 if ferr != nil { 332 return ferr 333 } 334 for _, v := range a.NS { 335 if v == fs { 336 return nil 337 } 338 } 339 a.NS = append(a.NS, fs) 340 return nil 341 } 342 343 // InsertNS_float64 works like InsertNS but takes a float64 344 func (a *AttributeValue) InsertNS_float64(f float64) error { 345 if a == nil { 346 return errors.New("AttributeValue.InsertNS_float64: pointer receiver is nil") 347 } 348 k := strconv.FormatFloat(f, 'f', -1, 64) 349 for _, v := range a.NS { 350 if v == k { 351 return nil 352 } 353 } 354 a.NS = append(a.NS, k) 355 return nil 356 } 357 358 // InsertBS adds a new base64 string to the bs (JSON: BS) set. 359 // String is parsed to make sure it is a represents a valid base64 blob. 360 // BS is *generated* from an internal representation (UM_bs) 361 // as it transforms a map into a list (a "set"). 362 // The argument is assumed to be already encoded by the caller. 363 func (a *AttributeValue) InsertBS(k string) error { 364 if a == nil { 365 return errors.New("AttributeValue.InsertBS: pointer receiver is nil") 366 } 367 berr := cast.AWSParseBinary(k) 368 if berr != nil { 369 return berr 370 } 371 for _, v := range a.BS { 372 if v == k { 373 return nil 374 } 375 } 376 a.BS = append(a.BS, k) 377 return nil 378 } 379 380 // InsertBS_unencoded adds a new plain string to the bs (JSON: BS) set. 381 // BS is *generated* from an internal representation (UM_bs) 382 // as it transforms a map into a list (a "set"). 383 // The argument is assumed to be plaintext and will be base64 encoded. 384 func (a *AttributeValue) InsertBS_unencoded(k string) error { 385 if a == nil { 386 return errors.New("AttributeValue.InsertBS_unencoded: pointer receiver is nil") 387 } 388 b64_k := base64.StdEncoding.EncodeToString([]byte(k)) 389 for _, v := range a.BS { 390 if v == b64_k { 391 return nil 392 } 393 } 394 a.BS = append(a.BS, b64_k) 395 return nil 396 } 397 398 // InsertL will append a pointer to a new AttributeValue v to the L list. 399 func (a *AttributeValue) InsertL(v *AttributeValue) error { 400 if a == nil { 401 return errors.New("AttributeValue.InsertL: pointer receiver is nil") 402 } 403 v_cp := NewAttributeValue() 404 cp_err := v.Copy(v_cp) 405 if cp_err != nil { 406 return cp_err 407 } 408 a.L = append(a.L, v_cp) 409 return nil 410 } 411 412 // InsertM will insert a pointer to a new AttributeValue v to the M map for key k. 413 // If k was previously set in the M map, the value will be overwritten. 414 func (a *AttributeValue) InsertM(k string, v *AttributeValue) error { 415 if a == nil { 416 return errors.New("AttributeValue.InsertM: pointer receiver is nil") 417 } 418 v_cp := NewAttributeValue() 419 cp_err := v.Copy(v_cp) 420 if cp_err != nil { 421 return cp_err 422 } 423 a.M[k] = v_cp 424 return nil 425 } 426 427 // InsertBOOL will set the BOOL field. 428 func (a *AttributeValue) InsertBOOL(b bool) error { 429 if a == nil { 430 return errors.New("AttributeValue.InsertBOOL: pointer receiver is nil") 431 } 432 if a.BOOL == nil { 433 a.BOOL = new(bool) 434 } 435 *a.BOOL = b 436 return nil 437 } 438 439 // InsertNULL will set the NULL field. 440 func (a *AttributeValue) InsertNULL(b bool) error { 441 if a == nil { 442 return errors.New("AttributeValue.InsertNULL: pointer receiver is nil") 443 } 444 if a.NULL == nil { 445 a.NULL = new(bool) 446 } 447 *a.NULL = b 448 return nil 449 } 450 451 // AttributeValueMap is used throughout GoDynamo 452 type AttributeValueMap map[string]*AttributeValue 453 454 func NewAttributeValueMap() AttributeValueMap { 455 m := make(map[string]*AttributeValue) 456 return m 457 } 458 459 // Copy makes a copy of the this AttributeValueMap into ac. 460 func (a AttributeValueMap) Copy(ac AttributeValueMap) error { 461 if a == nil { 462 return errors.New("AttributeValueMap.Copy: pointer receiver is nil") 463 } 464 if ac == nil { 465 return errors.New("AttributeValueMap.Copy: copy target attributeValueMap instance is nil") 466 } 467 for key, attributeValue := range a { 468 av_cp := NewAttributeValue() 469 if av_cp == nil { 470 return errors.New("AttributeValueMap.Copy: copy attributeValue is nil") 471 } 472 cp_err := attributeValue.Copy(av_cp) 473 if cp_err != nil { 474 e := fmt.Sprintf("AttributeValueMap.Copy: copy attributeValue err:%s", cp_err.Error()) 475 return errors.New(e) 476 } 477 ac[key] = av_cp 478 } 479 return nil 480 } 481 482 // AttributeValueUpdate is used in UpdateItem 483 type AttributeValueUpdate struct { 484 Action string `json:",omitempty"` 485 Value *AttributeValue `json:",omitempty"` 486 } 487 488 func NewAttributeValueUpdate() *AttributeValueUpdate { 489 a := new(AttributeValueUpdate) 490 a.Value = NewAttributeValue() 491 return a 492 } 493 494 type AttributeValueUpdateMap map[string]*AttributeValueUpdate 495 496 func NewAttributeValueUpdateMap() AttributeValueUpdateMap { 497 m := make(map[string]*AttributeValueUpdate) 498 return m 499 } 500 501 // BasicJSONToAttributeValueMap provides a lossy mapping from "basic" json to an AttributeValueMap. 502 // This allows for the type of "JSON Document" functionality employed in the 503 // current AWS SDK and outlined in the docs 504 // (see http://aws.amazon.com/blogs/aws/dynamodb-update-json-and-more/) 505 func BasicJSONToAttributeValueMap(b []byte) (AttributeValueMap, error) { 506 if b == nil { 507 return nil, errors.New("AttributeValue.Insert.BasicJSONToAttributeValueMap: arg is nil") 508 } 509 // unmarshal the arbitrary json 510 var i interface{} 511 um_err := json.Unmarshal(b, &i) 512 if um_err != nil { 513 return nil, um_err 514 } 515 return InterfaceToAttributeValueMap(i) 516 } 517 518 // InterfaceToAttributeValueMap attempts to coerce an appropriate interface {} to 519 // an AttributeValueMap 520 func InterfaceToAttributeValueMap(i interface{}) (AttributeValueMap, error) { 521 m, m_ok := i.(map[string]interface{}) 522 if !m_ok { 523 return nil, errors.New("AttributeValue.InterfaceToAttributeValueMap: top level unmarshal not (map[string] interface{})") 524 } 525 avm := NewAttributeValueMap() 526 for k, v := range m { 527 c, cerr := CoerceToAttributeValue(v) 528 if cerr != nil { 529 return nil, cerr 530 } 531 avm[k] = c 532 } 533 return avm, nil 534 } 535 536 // BasicJSONToAttributeValue provides a lossy mapping from "basic" json to an AttributeValue. 537 // This allows for the type of "JSON Document" functionality employed in the 538 // current AWS SDK and outlined in the docs 539 // (see http://aws.amazon.com/blogs/aws/dynamodb-update-json-and-more/) 540 func BasicJSONToAttributeValue(b []byte) (*AttributeValue, error) { 541 if b == nil { 542 return nil, errors.New("AttributeValue.Insert.BasicJSONToAttributeValue: arg is nil") 543 } 544 // unmarshal the arbitrary json 545 var i interface{} 546 um_err := json.Unmarshal(b, &i) 547 if um_err != nil { 548 return nil, um_err 549 } 550 return InterfaceToAttributeValue(i) 551 } 552 553 // InterfaceToAttributeValue attempts to coerce an appropriate interface {} to 554 // an *AttributeValue 555 func InterfaceToAttributeValue(i interface{}) (*AttributeValue, error) { 556 return CoerceToAttributeValue(i) 557 } 558 559 // CoerceToAttributeValue is a lossy translation for basic json to the AWS serialization format 560 // for AttributeValue. There are types that will be dropped as they are indistinguishable 561 // without their type designations: 562 // 1. binary will be dropped as the values will always be coerced to string. 563 // 2. null (as a type, not a value) will always be coerced to bool. 564 func CoerceToAttributeValue(i interface{}) (*AttributeValue, error) { 565 a := NewAttributeValue() 566 567 // bool (null also coerced to bool) 568 b, b_ok := i.(bool) 569 if b_ok { 570 a.BOOL = new(bool) 571 *a.BOOL = b 572 return a, nil 573 } 574 575 // number - float (the default unmarshal will always use this type) 576 n, n_ok := i.(float64) 577 if n_ok { 578 a.N = strconv.FormatFloat(n, 'f', -1, 64) 579 return a, nil 580 } 581 582 // string (binary also coerced to string) 583 s, s_ok := i.(string) 584 if s_ok { 585 a.S = s 586 return a, nil 587 } 588 589 // map of string -> *AttributeValue 590 m, m_ok := i.(map[string]interface{}) 591 if m_ok { 592 for k, v := range m { 593 a_child, a_child_err := CoerceToAttributeValue(v) 594 if a_child_err != nil { 595 return nil, a_child_err 596 } 597 a.M[k] = a_child 598 } 599 return a, nil 600 } 601 602 // the only type of list that is inferred by the generic unmarshal is []interface{}. 603 // we need to use further type inference to determine if the list can be made into 604 // an NS or SS...or is heterogenous and should be turned into an L 605 l, l_ok := i.([]interface{}) 606 if l_ok { 607 l_len := len(l) 608 609 // check first if the list is composed strictly of floats or strings. If so, 610 // then we can make a NS or SS list 611 float_vals := make([]float64, 0) 612 string_vals := make([]string, 0) 613 for _, u := range l { 614 f, f_ok := u.(float64) 615 if f_ok { 616 float_vals = append(float_vals, f) 617 } else { 618 s, s_ok := u.(string) 619 if s_ok { 620 string_vals = append(string_vals, s) 621 } 622 } 623 } 624 floats_len := len(float_vals) 625 strings_len := len(string_vals) 626 627 // the list is all floats, turn it into an NS 628 if (floats_len == l_len) && (strings_len == 0) { 629 for _, f := range float_vals { 630 ferr := a.InsertNS_float64(f) 631 if ferr != nil { 632 return nil, ferr 633 } 634 } 635 return a, nil 636 } 637 // the list is all strings, turn it into an SS 638 if (strings_len == l_len) && (floats_len == 0) { 639 for _, v := range string_vals { 640 _ = a.InsertSS(v) 641 } 642 return a, nil 643 } 644 645 // the list was not just strictly strings or floats 646 for _, v := range l { 647 a_child, a_child_err := CoerceToAttributeValue(v) 648 if a_child_err != nil { 649 return nil, a_child_err 650 } 651 a.L = append(a.L, a_child) 652 } 653 return a, nil 654 } 655 656 e := fmt.Sprintf("no coercion for %v", i) 657 return nil, errors.New(e) 658 } 659 660 // ToBasicJSON provides a mapping from an AttributeValueMap to basic json 661 // This allows for items from dynamo to be printed in a flat fashion if desired. 662 func (a AttributeValueMap) ToBasicJSON() ([]byte, error) { 663 c, cerr := a.ToInterface() 664 if cerr != nil { 665 return nil, cerr 666 } 667 b, merr := json.Marshal(c) 668 if merr != nil { 669 return nil, merr 670 } else { 671 return b, nil 672 } 673 } 674 675 // AttributeValueMapToInterface converts the map into a map of the key names to interface types 676 // that do not have type designations, and be marshaled into basic json 677 func (a AttributeValueMap) ToInterface() (interface{}, error) { 678 m := make(map[string]interface{}) 679 for k, v := range a { 680 c, cerr := v.ToInterface() 681 if cerr != nil { 682 return nil, cerr 683 } else { 684 m[k] = c 685 } 686 } 687 return m, nil 688 } 689 690 // ToBasicJSON provides a mapping from an AttributeValue to basic json 691 // This allows for items from dynamo to be printed in a flat fashion if desired. 692 func (a *AttributeValue) ToBasicJSON() ([]byte, error) { 693 if a == nil { 694 return nil, errors.New("AttributeValue.ToBasicJSON: pointer receiver is nil") 695 } 696 c, cerr := a.ToInterface() 697 if cerr != nil { 698 return nil, cerr 699 } 700 b, merr := json.Marshal(c) 701 if merr != nil { 702 return nil, merr 703 } else { 704 return b, nil 705 } 706 } 707 708 // AttributeValueToInterface strips the AttributeValue type designations and returns a structure 709 // that can be marshaled into basic json. 710 func (a *AttributeValue) ToInterface() (interface{}, error) { 711 if a == nil { 712 return "", errors.New("AttributeValue.ToInterface: pointer receiver is nil") 713 } 714 if a.BOOL != nil { 715 return *a.BOOL, nil 716 } 717 if a.NULL != nil { 718 return *a.NULL, nil 719 } 720 if a.S != "" { 721 return a.S, nil 722 } 723 if a.B != "" { 724 return a.B, nil 725 } 726 if a.N != "" { 727 f, ferr := strconv.ParseFloat(a.N, 64) 728 if ferr != nil { 729 return nil, ferr 730 } else { 731 return f, nil 732 } 733 } 734 if len(a.SS) != 0 { 735 return a.SS, nil 736 } 737 if len(a.BS) != 0 { 738 return a.BS, nil 739 } 740 ns_len := len(a.NS) 741 if ns_len != 0 { 742 ns := make([]float64, ns_len) 743 for i, n := range a.NS { 744 f, ferr := strconv.ParseFloat(n, 64) 745 if ferr != nil { 746 return nil, ferr 747 } else { 748 ns[i] = f 749 } 750 } 751 return ns, nil 752 } 753 l_len := len(a.L) 754 if l_len != 0 { 755 ls := make([]interface{}, l_len) 756 for i, v := range a.L { 757 c, cerr := v.ToInterface() 758 if cerr != nil { 759 return nil, cerr 760 } else { 761 ls[i] = c 762 } 763 } 764 return ls, nil 765 } 766 m_len := len(a.M) 767 if m_len != 0 { 768 m := make(map[string]interface{}) 769 for k, v := range a.M { 770 c, cerr := v.ToInterface() 771 if cerr != nil { 772 return nil, cerr 773 } else { 774 m[k] = c 775 } 776 } 777 return m, nil 778 } 779 e := fmt.Sprintf("no coercion for %v", a) 780 return nil, errors.New(e) 781 }