1package dynamodbattribute 2 3import ( 4 "math" 5 "testing" 6 7 "github.com/aws/aws-sdk-go/aws" 8 "github.com/aws/aws-sdk-go/aws/awserr" 9 "github.com/aws/aws-sdk-go/service/dynamodb" 10) 11 12type mySimpleStruct struct { 13 String string 14 Int int 15 Uint uint 16 Float32 float32 17 Float64 float64 18 Bool bool 19 Null *interface{} 20} 21 22type myComplexStruct struct { 23 Simple []mySimpleStruct 24} 25 26type converterTestInput struct { 27 input interface{} 28 expected interface{} 29 err awserr.Error 30 inputType string // "enum" of types 31} 32 33var trueValue = true 34var falseValue = false 35 36var converterScalarInputs = []converterTestInput{ 37 { 38 input: nil, 39 expected: &dynamodb.AttributeValue{NULL: &trueValue}, 40 }, 41 { 42 input: "some string", 43 expected: &dynamodb.AttributeValue{S: aws.String("some string")}, 44 }, 45 { 46 input: true, 47 expected: &dynamodb.AttributeValue{BOOL: &trueValue}, 48 }, 49 { 50 input: false, 51 expected: &dynamodb.AttributeValue{BOOL: &falseValue}, 52 }, 53 { 54 input: 3.14, 55 expected: &dynamodb.AttributeValue{N: aws.String("3.14")}, 56 }, 57 { 58 input: math.MaxFloat32, 59 expected: &dynamodb.AttributeValue{N: aws.String("340282346638528860000000000000000000000")}, 60 }, 61 { 62 input: math.MaxFloat64, 63 expected: &dynamodb.AttributeValue{N: aws.String("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")}, 64 }, 65 { 66 input: 12, 67 expected: &dynamodb.AttributeValue{N: aws.String("12")}, 68 }, 69 { 70 input: mySimpleStruct{}, 71 expected: &dynamodb.AttributeValue{ 72 M: map[string]*dynamodb.AttributeValue{ 73 "Bool": {BOOL: &falseValue}, 74 "Float32": {N: aws.String("0")}, 75 "Float64": {N: aws.String("0")}, 76 "Int": {N: aws.String("0")}, 77 "Null": {NULL: &trueValue}, 78 "String": {S: aws.String("")}, 79 "Uint": {N: aws.String("0")}, 80 }, 81 }, 82 inputType: "mySimpleStruct", 83 }, 84} 85 86var converterMapTestInputs = []converterTestInput{ 87 // Scalar tests 88 { 89 input: nil, 90 err: awserr.New("SerializationError", "in must be a map[string]interface{} or struct, got <nil>", nil), 91 }, 92 { 93 input: map[string]interface{}{"string": "some string"}, 94 expected: map[string]*dynamodb.AttributeValue{"string": {S: aws.String("some string")}}, 95 }, 96 { 97 input: map[string]interface{}{"bool": true}, 98 expected: map[string]*dynamodb.AttributeValue{"bool": {BOOL: &trueValue}}, 99 }, 100 { 101 input: map[string]interface{}{"bool": false}, 102 expected: map[string]*dynamodb.AttributeValue{"bool": {BOOL: &falseValue}}, 103 }, 104 { 105 input: map[string]interface{}{"null": nil}, 106 expected: map[string]*dynamodb.AttributeValue{"null": {NULL: &trueValue}}, 107 }, 108 { 109 input: map[string]interface{}{"float": 3.14}, 110 expected: map[string]*dynamodb.AttributeValue{"float": {N: aws.String("3.14")}}, 111 }, 112 { 113 input: map[string]interface{}{"float": math.MaxFloat32}, 114 expected: map[string]*dynamodb.AttributeValue{"float": {N: aws.String("340282346638528860000000000000000000000")}}, 115 }, 116 { 117 input: map[string]interface{}{"float": math.MaxFloat64}, 118 expected: map[string]*dynamodb.AttributeValue{"float": {N: aws.String("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")}}, 119 }, 120 { 121 input: map[string]interface{}{"int": int(12)}, 122 expected: map[string]*dynamodb.AttributeValue{"int": {N: aws.String("12")}}, 123 }, 124 { 125 input: map[string]interface{}{"byte": []byte{48, 49}}, 126 expected: map[string]*dynamodb.AttributeValue{"byte": {B: []byte{48, 49}}}, 127 }, 128 // List 129 { 130 input: map[string]interface{}{"list": []interface{}{"a string", 12, 3.14, true, nil, false}}, 131 expected: map[string]*dynamodb.AttributeValue{ 132 "list": { 133 L: []*dynamodb.AttributeValue{ 134 {S: aws.String("a string")}, 135 {N: aws.String("12")}, 136 {N: aws.String("3.14")}, 137 {BOOL: &trueValue}, 138 {NULL: &trueValue}, 139 {BOOL: &falseValue}, 140 }, 141 }, 142 }, 143 }, 144 // Map 145 { 146 input: map[string]interface{}{"map": map[string]interface{}{"nestedint": 12}}, 147 expected: map[string]*dynamodb.AttributeValue{ 148 "map": { 149 M: map[string]*dynamodb.AttributeValue{ 150 "nestedint": { 151 N: aws.String("12"), 152 }, 153 }, 154 }, 155 }, 156 }, 157 // Structs 158 { 159 input: mySimpleStruct{}, 160 expected: map[string]*dynamodb.AttributeValue{ 161 "Bool": {BOOL: &falseValue}, 162 "Float32": {N: aws.String("0")}, 163 "Float64": {N: aws.String("0")}, 164 "Int": {N: aws.String("0")}, 165 "Null": {NULL: &trueValue}, 166 "String": {S: aws.String("")}, 167 "Uint": {N: aws.String("0")}, 168 }, 169 inputType: "mySimpleStruct", 170 }, 171 { 172 input: myComplexStruct{}, 173 expected: map[string]*dynamodb.AttributeValue{ 174 "Simple": {NULL: &trueValue}, 175 }, 176 inputType: "myComplexStruct", 177 }, 178 { 179 input: myComplexStruct{Simple: []mySimpleStruct{{Int: -2}, {Uint: 5}}}, 180 expected: map[string]*dynamodb.AttributeValue{ 181 "Simple": { 182 L: []*dynamodb.AttributeValue{ 183 { 184 M: map[string]*dynamodb.AttributeValue{ 185 "Bool": {BOOL: &falseValue}, 186 "Float32": {N: aws.String("0")}, 187 "Float64": {N: aws.String("0")}, 188 "Int": {N: aws.String("-2")}, 189 "Null": {NULL: &trueValue}, 190 "String": {S: aws.String("")}, 191 "Uint": {N: aws.String("0")}, 192 }, 193 }, 194 { 195 M: map[string]*dynamodb.AttributeValue{ 196 "Bool": {BOOL: &falseValue}, 197 "Float32": {N: aws.String("0")}, 198 "Float64": {N: aws.String("0")}, 199 "Int": {N: aws.String("0")}, 200 "Null": {NULL: &trueValue}, 201 "String": {S: aws.String("")}, 202 "Uint": {N: aws.String("5")}, 203 }, 204 }, 205 }, 206 }, 207 }, 208 inputType: "myComplexStruct", 209 }, 210} 211 212var converterListTestInputs = []converterTestInput{ 213 { 214 input: nil, 215 err: awserr.New("SerializationError", "in must be an array or slice, got <nil>", nil), 216 }, 217 { 218 input: []interface{}{}, 219 expected: []*dynamodb.AttributeValue{}, 220 }, 221 { 222 input: []interface{}{"a string", 12, 3.14, true, nil, false}, 223 expected: []*dynamodb.AttributeValue{ 224 {S: aws.String("a string")}, 225 {N: aws.String("12")}, 226 {N: aws.String("3.14")}, 227 {BOOL: &trueValue}, 228 {NULL: &trueValue}, 229 {BOOL: &falseValue}, 230 }, 231 }, 232 { 233 input: []mySimpleStruct{{}}, 234 expected: []*dynamodb.AttributeValue{ 235 { 236 M: map[string]*dynamodb.AttributeValue{ 237 "Bool": {BOOL: &falseValue}, 238 "Float32": {N: aws.String("0")}, 239 "Float64": {N: aws.String("0")}, 240 "Int": {N: aws.String("0")}, 241 "Null": {NULL: &trueValue}, 242 "String": {S: aws.String("")}, 243 "Uint": {N: aws.String("0")}, 244 }, 245 }, 246 }, 247 inputType: "mySimpleStruct", 248 }, 249} 250 251func TestConvertTo(t *testing.T) { 252 for _, test := range converterScalarInputs { 253 testConvertTo(t, test) 254 } 255} 256 257func testConvertTo(t *testing.T, test converterTestInput) { 258 actual, err := ConvertTo(test.input) 259 if test.err != nil { 260 if err == nil { 261 t.Errorf("ConvertTo with input %#v retured %#v, expected error `%s`", test.input, actual, test.err) 262 } else if err.Error() != test.err.Error() { 263 t.Errorf("ConvertTo with input %#v retured error `%s`, expected error `%s`", test.input, err, test.err) 264 } 265 } else { 266 if err != nil { 267 t.Errorf("ConvertTo with input %#v retured error `%s`", test.input, err) 268 } 269 compareObjects(t, test.expected, actual) 270 } 271} 272 273func TestConvertFrom(t *testing.T) { 274 // Using the same inputs from TestConvertTo, test the reverse mapping. 275 for _, test := range converterScalarInputs { 276 if test.expected != nil { 277 testConvertFrom(t, test) 278 } 279 } 280} 281 282func testConvertFrom(t *testing.T, test converterTestInput) { 283 switch test.inputType { 284 case "mySimpleStruct": 285 var actual mySimpleStruct 286 if err := ConvertFrom(test.expected.(*dynamodb.AttributeValue), &actual); err != nil { 287 t.Errorf("ConvertFrom with input %#v retured error `%s`", test.expected, err) 288 } 289 compareObjects(t, test.input, actual) 290 case "myComplexStruct": 291 var actual myComplexStruct 292 if err := ConvertFrom(test.expected.(*dynamodb.AttributeValue), &actual); err != nil { 293 t.Errorf("ConvertFrom with input %#v retured error `%s`", test.expected, err) 294 } 295 compareObjects(t, test.input, actual) 296 default: 297 var actual interface{} 298 if err := ConvertFrom(test.expected.(*dynamodb.AttributeValue), &actual); err != nil { 299 t.Errorf("ConvertFrom with input %#v retured error `%s`", test.expected, err) 300 } 301 compareObjects(t, test.input, actual) 302 } 303} 304 305func TestConvertFromError(t *testing.T) { 306 // Test that we get an error using ConvertFrom to convert to a map. 307 var actual map[string]interface{} 308 expected := awserr.New("SerializationError", `v must be a non-nil pointer to an interface{} or struct, got *map[string]interface {}`, nil).Error() 309 if err := ConvertFrom(nil, &actual); err == nil { 310 t.Errorf("ConvertFrom with input %#v returned no error, expected error `%s`", nil, expected) 311 } else if err.Error() != expected { 312 t.Errorf("ConvertFrom with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 313 } 314 315 // Test that we get an error using ConvertFrom to convert to a list. 316 var actual2 []interface{} 317 expected = awserr.New("SerializationError", `v must be a non-nil pointer to an interface{} or struct, got *[]interface {}`, nil).Error() 318 if err := ConvertFrom(nil, &actual2); err == nil { 319 t.Errorf("ConvertFrom with input %#v returned no error, expected error `%s`", nil, expected) 320 } else if err.Error() != expected { 321 t.Errorf("ConvertFrom with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 322 } 323} 324 325func TestConvertToMap(t *testing.T) { 326 for _, test := range converterMapTestInputs { 327 testConvertToMap(t, test) 328 } 329} 330 331func testConvertToMap(t *testing.T, test converterTestInput) { 332 actual, err := ConvertToMap(test.input) 333 if test.err != nil { 334 if err == nil { 335 t.Errorf("ConvertToMap with input %#v retured %#v, expected error `%s`", test.input, actual, test.err) 336 } else if err.Error() != test.err.Error() { 337 t.Errorf("ConvertToMap with input %#v retured error `%s`, expected error `%s`", test.input, err, test.err) 338 } 339 } else { 340 if err != nil { 341 t.Errorf("ConvertToMap with input %#v retured error `%s`", test.input, err) 342 } 343 compareObjects(t, test.expected, actual) 344 } 345} 346 347func TestConvertFromMap(t *testing.T) { 348 // Using the same inputs from TestConvertToMap, test the reverse mapping. 349 for _, test := range converterMapTestInputs { 350 if test.expected != nil { 351 testConvertFromMap(t, test) 352 } 353 } 354} 355 356func testConvertFromMap(t *testing.T, test converterTestInput) { 357 switch test.inputType { 358 case "mySimpleStruct": 359 var actual mySimpleStruct 360 if err := ConvertFromMap(test.expected.(map[string]*dynamodb.AttributeValue), &actual); err != nil { 361 t.Errorf("ConvertFromMap with input %#v retured error `%s`", test.expected, err) 362 } 363 compareObjects(t, test.input, actual) 364 case "myComplexStruct": 365 var actual myComplexStruct 366 if err := ConvertFromMap(test.expected.(map[string]*dynamodb.AttributeValue), &actual); err != nil { 367 t.Errorf("ConvertFromMap with input %#v retured error `%s`", test.expected, err) 368 } 369 compareObjects(t, test.input, actual) 370 default: 371 var actual map[string]interface{} 372 if err := ConvertFromMap(test.expected.(map[string]*dynamodb.AttributeValue), &actual); err != nil { 373 t.Errorf("ConvertFromMap with input %#v retured error `%s`", test.expected, err) 374 } 375 compareObjects(t, test.input, actual) 376 } 377} 378 379func TestConvertFromMapError(t *testing.T) { 380 // Test that we get an error using ConvertFromMap to convert to an interface{}. 381 var actual interface{} 382 expected := awserr.New("SerializationError", `v must be a non-nil pointer to a map[string]interface{} or struct, got *interface {}`, nil).Error() 383 if err := ConvertFromMap(nil, &actual); err == nil { 384 t.Errorf("ConvertFromMap with input %#v returned no error, expected error `%s`", nil, expected) 385 } else if err.Error() != expected { 386 t.Errorf("ConvertFromMap with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 387 } 388 389 // Test that we get an error using ConvertFromMap to convert to a slice. 390 var actual2 []interface{} 391 expected = awserr.New("SerializationError", `v must be a non-nil pointer to a map[string]interface{} or struct, got *[]interface {}`, nil).Error() 392 if err := ConvertFromMap(nil, &actual2); err == nil { 393 t.Errorf("ConvertFromMap with input %#v returned no error, expected error `%s`", nil, expected) 394 } else if err.Error() != expected { 395 t.Errorf("ConvertFromMap with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 396 } 397} 398 399func TestConvertToList(t *testing.T) { 400 for _, test := range converterListTestInputs { 401 testConvertToList(t, test) 402 } 403} 404 405func testConvertToList(t *testing.T, test converterTestInput) { 406 actual, err := ConvertToList(test.input) 407 if test.err != nil { 408 if err == nil { 409 t.Errorf("ConvertToList with input %#v retured %#v, expected error `%s`", test.input, actual, test.err) 410 } else if err.Error() != test.err.Error() { 411 t.Errorf("ConvertToList with input %#v retured error `%s`, expected error `%s`", test.input, err, test.err) 412 } 413 } else { 414 if err != nil { 415 t.Errorf("ConvertToList with input %#v retured error `%s`", test.input, err) 416 } 417 compareObjects(t, test.expected, actual) 418 } 419} 420 421func TestConvertFromList(t *testing.T) { 422 // Using the same inputs from TestConvertToList, test the reverse mapping. 423 for _, test := range converterListTestInputs { 424 if test.expected != nil { 425 testConvertFromList(t, test) 426 } 427 } 428} 429 430func testConvertFromList(t *testing.T, test converterTestInput) { 431 switch test.inputType { 432 case "mySimpleStruct": 433 var actual []mySimpleStruct 434 if err := ConvertFromList(test.expected.([]*dynamodb.AttributeValue), &actual); err != nil { 435 t.Errorf("ConvertFromList with input %#v retured error `%s`", test.expected, err) 436 } 437 compareObjects(t, test.input, actual) 438 case "myComplexStruct": 439 var actual []myComplexStruct 440 if err := ConvertFromList(test.expected.([]*dynamodb.AttributeValue), &actual); err != nil { 441 t.Errorf("ConvertFromList with input %#v retured error `%s`", test.expected, err) 442 } 443 compareObjects(t, test.input, actual) 444 default: 445 var actual []interface{} 446 if err := ConvertFromList(test.expected.([]*dynamodb.AttributeValue), &actual); err != nil { 447 t.Errorf("ConvertFromList with input %#v retured error `%s`", test.expected, err) 448 } 449 compareObjects(t, test.input, actual) 450 } 451} 452 453func TestConvertFromListError(t *testing.T) { 454 // Test that we get an error using ConvertFromList to convert to a map. 455 var actual map[string]interface{} 456 expected := awserr.New("SerializationError", `v must be a non-nil pointer to an array or slice, got *map[string]interface {}`, nil).Error() 457 if err := ConvertFromList(nil, &actual); err == nil { 458 t.Errorf("ConvertFromList with input %#v returned no error, expected error `%s`", nil, expected) 459 } else if err.Error() != expected { 460 t.Errorf("ConvertFromList with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 461 } 462 463 // Test that we get an error using ConvertFromList to convert to a struct. 464 var actual2 myComplexStruct 465 expected = awserr.New("SerializationError", `v must be a non-nil pointer to an array or slice, got *dynamodbattribute.myComplexStruct`, nil).Error() 466 if err := ConvertFromList(nil, &actual2); err == nil { 467 t.Errorf("ConvertFromList with input %#v returned no error, expected error `%s`", nil, expected) 468 } else if err.Error() != expected { 469 t.Errorf("ConvertFromList with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 470 } 471 472 // Test that we get an error using ConvertFromList to convert to an interface{}. 473 var actual3 interface{} 474 expected = awserr.New("SerializationError", `v must be a non-nil pointer to an array or slice, got *interface {}`, nil).Error() 475 if err := ConvertFromList(nil, &actual3); err == nil { 476 t.Errorf("ConvertFromList with input %#v returned no error, expected error `%s`", nil, expected) 477 } else if err.Error() != expected { 478 t.Errorf("ConvertFromList with input %#v returned error `%s`, expected error `%s`", nil, err, expected) 479 } 480} 481 482func BenchmarkConvertTo(b *testing.B) { 483 d := mySimpleStruct{ 484 String: "abc", 485 Int: 123, 486 Uint: 123, 487 Float32: 123.321, 488 Float64: 123.321, 489 Bool: true, 490 Null: nil, 491 } 492 for i := 0; i < b.N; i++ { 493 _, err := ConvertTo(d) 494 if err != nil { 495 b.Fatal("unexpected error", err) 496 } 497 } 498} 499