1package dynamodbattribute 2 3import ( 4 "reflect" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/service/dynamodb" 11) 12 13type testBinarySetStruct struct { 14 Binarys [][]byte `dynamodbav:",binaryset"` 15} 16type testNumberSetStruct struct { 17 Numbers []int `dynamodbav:",numberset"` 18} 19type testStringSetStruct struct { 20 Strings []string `dynamodbav:",stringset"` 21} 22 23type testIntAsStringStruct struct { 24 Value int `dynamodbav:",string"` 25} 26 27type testOmitEmptyStruct struct { 28 Value string `dynamodbav:",omitempty"` 29 Value2 *string `dynamodbav:",omitempty"` 30 Value3 int 31} 32 33type testAliasedString string 34type testAliasedStringSlice []string 35type testAliasedInt int 36type testAliasedIntSlice []int 37type testAliasedMap map[string]int 38type testAliasedSlice []string 39type testAliasedByteSlice []byte 40type testAliasedBool bool 41type testAliasedBoolSlice []bool 42 43type testAliasedStruct struct { 44 Value testAliasedString 45 Value2 testAliasedInt 46 Value3 testAliasedMap 47 Value4 testAliasedSlice 48 49 Value5 testAliasedByteSlice 50 Value6 []testAliasedInt 51 Value7 []testAliasedString 52 53 Value8 []testAliasedByteSlice `dynamodbav:",binaryset"` 54 Value9 []testAliasedInt `dynamodbav:",numberset"` 55 Value10 []testAliasedString `dynamodbav:",stringset"` 56 57 Value11 testAliasedIntSlice 58 Value12 testAliasedStringSlice 59 60 Value13 testAliasedBool 61 Value14 testAliasedBoolSlice 62 63 Value15 map[testAliasedString]string 64} 65 66type testNamedPointer *int 67 68var testDate, _ = time.Parse(time.RFC3339, "2016-05-03T17:06:26.209072Z") 69 70var sharedTestCases = []struct { 71 in *dynamodb.AttributeValue 72 actual, expected interface{} 73 encoderOpts func(encoder *Encoder) 74 enableEmptyString bool 75 enableEmptyByteSlice bool 76 err error 77}{ 78 0: { // Binary slice 79 in: &dynamodb.AttributeValue{B: []byte{48, 49}}, 80 actual: &[]byte{}, 81 expected: []byte{48, 49}, 82 }, 83 1: { // Binary slice oversized 84 in: &dynamodb.AttributeValue{B: []byte{48, 49}}, 85 actual: func() *[]byte { 86 v := make([]byte, 0, 10) 87 return &v 88 }(), 89 expected: []byte{48, 49}, 90 }, 91 2: { // Binary slice pointer 92 in: &dynamodb.AttributeValue{B: []byte{48, 49}}, 93 actual: func() **[]byte { 94 v := make([]byte, 0, 10) 95 v2 := &v 96 return &v2 97 }(), 98 expected: []byte{48, 49}, 99 }, 100 3: { // Bool 101 in: &dynamodb.AttributeValue{BOOL: aws.Bool(true)}, 102 actual: new(bool), 103 expected: true, 104 }, 105 4: { // List 106 in: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ 107 {N: aws.String("123")}, 108 }}, 109 actual: &[]int{}, 110 expected: []int{123}, 111 }, 112 5: { // Map, interface 113 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 114 "abc": {N: aws.String("123")}, 115 }}, 116 actual: &map[string]int{}, 117 expected: map[string]int{"abc": 123}, 118 }, 119 6: { // Map, struct 120 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 121 "Abc": {N: aws.String("123")}, 122 }}, 123 actual: &struct{ Abc int }{}, 124 expected: struct{ Abc int }{Abc: 123}, 125 }, 126 7: { // Map, struct 127 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 128 "abc": {N: aws.String("123")}, 129 }}, 130 actual: &struct { 131 Abc int `json:"abc" dynamodbav:"abc"` 132 }{}, 133 expected: struct { 134 Abc int `json:"abc" dynamodbav:"abc"` 135 }{Abc: 123}, 136 }, 137 8: { // Number, int 138 in: &dynamodb.AttributeValue{N: aws.String("123")}, 139 actual: new(int), 140 expected: 123, 141 }, 142 9: { // Number, Float 143 in: &dynamodb.AttributeValue{N: aws.String("123.1")}, 144 actual: new(float64), 145 expected: float64(123.1), 146 }, 147 10: { // Null string 148 in: &dynamodb.AttributeValue{NULL: aws.Bool(true)}, 149 actual: new(string), 150 expected: "", 151 }, 152 11: { // Null ptr string 153 in: &dynamodb.AttributeValue{NULL: aws.Bool(true)}, 154 actual: new(*string), 155 expected: nil, 156 }, 157 12: { // Non-Empty String 158 in: &dynamodb.AttributeValue{S: aws.String("abc")}, 159 actual: new(string), 160 expected: "abc", 161 }, 162 13: { // Binary Set 163 in: &dynamodb.AttributeValue{ 164 M: map[string]*dynamodb.AttributeValue{ 165 "Binarys": {BS: [][]byte{{48, 49}, {50, 51}}}, 166 }, 167 }, 168 actual: &testBinarySetStruct{}, 169 expected: testBinarySetStruct{Binarys: [][]byte{{48, 49}, {50, 51}}}, 170 }, 171 14: { // Number Set 172 in: &dynamodb.AttributeValue{ 173 M: map[string]*dynamodb.AttributeValue{ 174 "Numbers": {NS: []*string{aws.String("123"), aws.String("321")}}, 175 }, 176 }, 177 actual: &testNumberSetStruct{}, 178 expected: testNumberSetStruct{Numbers: []int{123, 321}}, 179 }, 180 15: { // String Set 181 in: &dynamodb.AttributeValue{ 182 M: map[string]*dynamodb.AttributeValue{ 183 "Strings": {SS: []*string{aws.String("abc"), aws.String("efg")}}, 184 }, 185 }, 186 actual: &testStringSetStruct{}, 187 expected: testStringSetStruct{Strings: []string{"abc", "efg"}}, 188 }, 189 16: { // Int value as string 190 in: &dynamodb.AttributeValue{ 191 M: map[string]*dynamodb.AttributeValue{ 192 "Value": {S: aws.String("123")}, 193 }, 194 }, 195 actual: &testIntAsStringStruct{}, 196 expected: testIntAsStringStruct{Value: 123}, 197 }, 198 17: { // Omitempty 199 in: &dynamodb.AttributeValue{ 200 M: map[string]*dynamodb.AttributeValue{ 201 "Value3": {N: aws.String("0")}, 202 }, 203 }, 204 actual: &testOmitEmptyStruct{}, 205 expected: testOmitEmptyStruct{Value: "", Value2: nil, Value3: 0}, 206 }, 207 18: { // aliased type 208 in: &dynamodb.AttributeValue{ 209 M: map[string]*dynamodb.AttributeValue{ 210 "Value": {S: aws.String("123")}, 211 "Value2": {N: aws.String("123")}, 212 "Value3": {M: map[string]*dynamodb.AttributeValue{ 213 "Key": {N: aws.String("321")}, 214 }}, 215 "Value4": {L: []*dynamodb.AttributeValue{ 216 {S: aws.String("1")}, 217 {S: aws.String("2")}, 218 {S: aws.String("3")}, 219 }}, 220 "Value5": {B: []byte{0, 1, 2}}, 221 "Value6": {L: []*dynamodb.AttributeValue{ 222 {N: aws.String("1")}, 223 {N: aws.String("2")}, 224 {N: aws.String("3")}, 225 }}, 226 "Value7": {L: []*dynamodb.AttributeValue{ 227 {S: aws.String("1")}, 228 {S: aws.String("2")}, 229 {S: aws.String("3")}, 230 }}, 231 "Value8": {BS: [][]byte{ 232 {0, 1, 2}, {3, 4, 5}, 233 }}, 234 "Value9": {NS: []*string{ 235 aws.String("1"), 236 aws.String("2"), 237 aws.String("3"), 238 }}, 239 "Value10": {SS: []*string{ 240 aws.String("1"), 241 aws.String("2"), 242 aws.String("3"), 243 }}, 244 "Value11": {L: []*dynamodb.AttributeValue{ 245 {N: aws.String("1")}, 246 {N: aws.String("2")}, 247 {N: aws.String("3")}, 248 }}, 249 "Value12": {L: []*dynamodb.AttributeValue{ 250 {S: aws.String("1")}, 251 {S: aws.String("2")}, 252 {S: aws.String("3")}, 253 }}, 254 "Value13": {BOOL: aws.Bool(true)}, 255 "Value14": {L: []*dynamodb.AttributeValue{ 256 {BOOL: aws.Bool(true)}, 257 {BOOL: aws.Bool(false)}, 258 {BOOL: aws.Bool(true)}, 259 }}, 260 "Value15": {M: map[string]*dynamodb.AttributeValue{ 261 "TestKey": {S: aws.String("TestElement")}, 262 }}, 263 }, 264 }, 265 actual: &testAliasedStruct{}, 266 expected: testAliasedStruct{ 267 Value: "123", Value2: 123, 268 Value3: testAliasedMap{ 269 "Key": 321, 270 }, 271 Value4: testAliasedSlice{"1", "2", "3"}, 272 Value5: testAliasedByteSlice{0, 1, 2}, 273 Value6: []testAliasedInt{1, 2, 3}, 274 Value7: []testAliasedString{"1", "2", "3"}, 275 Value8: []testAliasedByteSlice{ 276 {0, 1, 2}, 277 {3, 4, 5}, 278 }, 279 Value9: []testAliasedInt{1, 2, 3}, 280 Value10: []testAliasedString{"1", "2", "3"}, 281 Value11: testAliasedIntSlice{1, 2, 3}, 282 Value12: testAliasedStringSlice{"1", "2", "3"}, 283 Value13: true, 284 Value14: testAliasedBoolSlice{true, false, true}, 285 Value15: map[testAliasedString]string{ 286 "TestKey": "TestElement", 287 }, 288 }, 289 }, 290 19: { 291 in: &dynamodb.AttributeValue{N: aws.String("123")}, 292 actual: new(testNamedPointer), 293 expected: testNamedPointer(aws.Int(123)), 294 }, 295 20: { // time.Time 296 in: &dynamodb.AttributeValue{S: aws.String("2016-05-03T17:06:26.209072Z")}, 297 actual: new(time.Time), 298 expected: testDate, 299 }, 300 21: { // time.Time List 301 in: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ 302 {S: aws.String("2016-05-03T17:06:26.209072Z")}, 303 {S: aws.String("2016-05-04T17:06:26.209072Z")}, 304 }}, 305 actual: new([]time.Time), 306 expected: []time.Time{testDate, testDate.Add(24 * time.Hour)}, 307 }, 308 22: { // time.Time struct 309 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 310 "abc": {S: aws.String("2016-05-03T17:06:26.209072Z")}, 311 }}, 312 actual: &struct { 313 Abc time.Time `json:"abc" dynamodbav:"abc"` 314 }{}, 315 expected: struct { 316 Abc time.Time `json:"abc" dynamodbav:"abc"` 317 }{Abc: testDate}, 318 }, 319 23: { // time.Time ptr struct 320 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 321 "abc": {S: aws.String("2016-05-03T17:06:26.209072Z")}, 322 }}, 323 actual: &struct { 324 Abc *time.Time `json:"abc" dynamodbav:"abc"` 325 }{}, 326 expected: struct { 327 Abc *time.Time `json:"abc" dynamodbav:"abc"` 328 }{Abc: &testDate}, 329 }, 330 24: { // empty string and NullEmptyString off 331 encoderOpts: func(encoder *Encoder) { 332 encoder.NullEmptyString = false 333 }, 334 in: &dynamodb.AttributeValue{S: aws.String("")}, 335 actual: new(string), 336 expected: "", 337 }, 338 25: { // empty ptr string and NullEmptyString off 339 encoderOpts: func(encoder *Encoder) { 340 encoder.NullEmptyString = false 341 }, 342 in: &dynamodb.AttributeValue{S: aws.String("")}, 343 actual: new(*string), 344 expected: "", 345 }, 346 26: { // string set with empty string and NullEmptyString off 347 encoderOpts: func(encoder *Encoder) { 348 encoder.NullEmptyString = false 349 }, 350 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 351 "Value": {SS: []*string{aws.String("one"), aws.String(""), aws.String("three")}}, 352 }}, 353 actual: &struct { 354 Value []string `dynamodbav:",stringset"` 355 }{}, 356 expected: struct { 357 Value []string `dynamodbav:",stringset"` 358 }{ 359 Value: []string{"one", "", "three"}, 360 }, 361 }, 362 27: { // empty byte slice and NullEmptyString off 363 encoderOpts: func(encoder *Encoder) { 364 encoder.NullEmptyByteSlice = false 365 }, 366 in: &dynamodb.AttributeValue{B: []byte{}}, 367 actual: &[]byte{}, 368 expected: []byte{}, 369 }, 370 28: { // byte slice set with empty values and NullEmptyString off 371 encoderOpts: func(encoder *Encoder) { 372 encoder.NullEmptyByteSlice = false 373 }, 374 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{"Value": {BS: [][]byte{{0x0}, {}, {0x2}}}}}, 375 actual: &struct { 376 Value [][]byte `dynamodbav:",binaryset"` 377 }{}, 378 expected: struct { 379 Value [][]byte `dynamodbav:",binaryset"` 380 }{ 381 Value: [][]byte{{0x0}, {}, {0x2}}, 382 }, 383 }, 384 29: { // empty byte slice and NullEmptyByteSlice disabled, and omitempty 385 encoderOpts: func(encoder *Encoder) { 386 encoder.NullEmptyByteSlice = false 387 }, 388 in: &dynamodb.AttributeValue{ 389 NULL: aws.Bool(true), 390 }, 391 actual: &struct { 392 Value []byte `dynamodbav:",omitempty"` 393 }{ 394 Value: []byte{}, 395 }, 396 expected: &struct { 397 Value []byte `dynamodbav:",omitempty"` 398 }{}, 399 }, 400} 401 402var sharedListTestCases = []struct { 403 in []*dynamodb.AttributeValue 404 actual, expected interface{} 405 err error 406}{ 407 { 408 in: []*dynamodb.AttributeValue{ 409 {B: []byte{48, 49}}, 410 {BOOL: aws.Bool(true)}, 411 {N: aws.String("123")}, 412 {S: aws.String("123")}, 413 }, 414 actual: func() *[]interface{} { 415 v := []interface{}{} 416 return &v 417 }(), 418 expected: []interface{}{[]byte{48, 49}, true, 123., "123"}, 419 }, 420 { 421 in: []*dynamodb.AttributeValue{ 422 {N: aws.String("1")}, 423 {N: aws.String("2")}, 424 {N: aws.String("3")}, 425 }, 426 actual: &[]interface{}{}, 427 expected: []interface{}{1., 2., 3.}, 428 }, 429} 430 431var sharedMapTestCases = []struct { 432 in map[string]*dynamodb.AttributeValue 433 actual, expected interface{} 434 err error 435}{ 436 { 437 in: map[string]*dynamodb.AttributeValue{ 438 "B": {B: []byte{48, 49}}, 439 "BOOL": {BOOL: aws.Bool(true)}, 440 "N": {N: aws.String("123")}, 441 "S": {S: aws.String("123")}, 442 }, 443 actual: &map[string]interface{}{}, 444 expected: map[string]interface{}{ 445 "B": []byte{48, 49}, "BOOL": true, 446 "N": 123., "S": "123", 447 }, 448 }, 449} 450 451func assertConvertTest(t *testing.T, i int, actual, expected interface{}, err, expectedErr error) { 452 if expectedErr != nil { 453 if err != nil { 454 if e, a := expectedErr, err; !strings.Contains(a.Error(), e.Error()) { 455 t.Errorf("case %d expect %v, got %v", i, e, a) 456 } 457 } else { 458 t.Fatalf("case %d, expected error, %v", i, expectedErr) 459 } 460 } else if err != nil { 461 t.Fatalf("case %d, expect no error, got %v", i, err) 462 } else { 463 if e, a := ptrToValue(expected), ptrToValue(actual); !reflect.DeepEqual(e, a) { 464 t.Errorf("case %d, expect %v, got %v", i, e, a) 465 } 466 } 467} 468 469func ptrToValue(in interface{}) interface{} { 470 v := reflect.ValueOf(in) 471 if v.Kind() == reflect.Ptr { 472 v = v.Elem() 473 } 474 if !v.IsValid() { 475 return nil 476 } 477 if v.Kind() == reflect.Ptr { 478 return ptrToValue(v.Interface()) 479 } 480 return v.Interface() 481} 482