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