1package dynamodbattribute
2
3import (
4	"fmt"
5	"reflect"
6	"testing"
7	"time"
8
9	"github.com/aws/aws-sdk-go/aws"
10	"github.com/aws/aws-sdk-go/aws/awserr"
11	"github.com/aws/aws-sdk-go/service/dynamodb"
12)
13
14func TestMarshalErrorTypes(t *testing.T) {
15	var _ awserr.Error = (*InvalidMarshalError)(nil)
16	var _ awserr.Error = (*unsupportedMarshalTypeError)(nil)
17}
18
19func TestMarshalShared(t *testing.T) {
20	for i, c := range sharedTestCases {
21		av, err := Marshal(c.expected)
22		assertConvertTest(t, i, av, c.in, err, c.err)
23	}
24}
25
26func TestMarshalListShared(t *testing.T) {
27	for i, c := range sharedListTestCases {
28		av, err := MarshalList(c.expected)
29		assertConvertTest(t, i, av, c.in, err, c.err)
30	}
31}
32
33func TestMarshalMapShared(t *testing.T) {
34	for i, c := range sharedMapTestCases {
35		av, err := MarshalMap(c.expected)
36		assertConvertTest(t, i, av, c.in, err, c.err)
37	}
38}
39
40type marshalMarshaler struct {
41	Value  string
42	Value2 int
43	Value3 bool
44	Value4 time.Time
45}
46
47func (m *marshalMarshaler) MarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error {
48	av.M = map[string]*dynamodb.AttributeValue{
49		"abc": {S: &m.Value},
50		"def": {N: aws.String(fmt.Sprintf("%d", m.Value2))},
51		"ghi": {BOOL: &m.Value3},
52		"jkl": {S: aws.String(m.Value4.Format(time.RFC3339Nano))},
53	}
54
55	return nil
56}
57
58func TestMarshalMashaler(t *testing.T) {
59	m := &marshalMarshaler{
60		Value:  "value",
61		Value2: 123,
62		Value3: true,
63		Value4: testDate,
64	}
65
66	expect := &dynamodb.AttributeValue{
67		M: map[string]*dynamodb.AttributeValue{
68			"abc": {S: aws.String("value")},
69			"def": {N: aws.String("123")},
70			"ghi": {BOOL: aws.Bool(true)},
71			"jkl": {S: aws.String("2016-05-03T17:06:26.209072Z")},
72		},
73	}
74
75	actual, err := Marshal(m)
76	if err != nil {
77		t.Errorf("expect nil, got %v", err)
78	}
79
80	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
81		t.Errorf("expect %v, got %v", e, a)
82	}
83}
84
85type testOmitEmptyElemListStruct struct {
86	Values []string `dynamodbav:",omitemptyelem"`
87}
88
89type testOmitEmptyElemMapStruct struct {
90	Values map[string]interface{} `dynamodbav:",omitemptyelem"`
91}
92
93func TestMarshalListOmitEmptyElem(t *testing.T) {
94	expect := &dynamodb.AttributeValue{
95		M: map[string]*dynamodb.AttributeValue{
96			"Values": {L: []*dynamodb.AttributeValue{
97				{S: aws.String("abc")},
98				{S: aws.String("123")},
99			}},
100		},
101	}
102
103	m := testOmitEmptyElemListStruct{Values: []string{"abc", "", "123"}}
104
105	actual, err := Marshal(m)
106	if err != nil {
107		t.Errorf("expect nil, got %v", err)
108	}
109	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
110		t.Errorf("expect %v, got %v", e, a)
111	}
112}
113
114func TestMarshalMapOmitEmptyElem(t *testing.T) {
115	expect := &dynamodb.AttributeValue{
116		M: map[string]*dynamodb.AttributeValue{
117			"Values": {M: map[string]*dynamodb.AttributeValue{
118				"abc": {N: aws.String("123")},
119				"klm": {S: aws.String("abc")},
120			}},
121		},
122	}
123
124	m := testOmitEmptyElemMapStruct{Values: map[string]interface{}{
125		"abc": 123.,
126		"efg": nil,
127		"hij": "",
128		"klm": "abc",
129	}}
130
131	actual, err := Marshal(m)
132	if err != nil {
133		t.Errorf("expect nil, got %v", err)
134	}
135	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
136		t.Errorf("expect %v, got %v", e, a)
137	}
138}
139
140type testOmitEmptyScalar struct {
141	IntZero       int  `dynamodbav:",omitempty"`
142	IntPtrNil     *int `dynamodbav:",omitempty"`
143	IntPtrSetZero *int `dynamodbav:",omitempty"`
144}
145
146func TestMarshalOmitEmpty(t *testing.T) {
147	expect := &dynamodb.AttributeValue{
148		M: map[string]*dynamodb.AttributeValue{
149			"IntPtrSetZero": {N: aws.String("0")},
150		},
151	}
152
153	m := testOmitEmptyScalar{IntPtrSetZero: aws.Int(0)}
154
155	actual, err := Marshal(m)
156	if err != nil {
157		t.Errorf("expect nil, got %v", err)
158	}
159	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
160		t.Errorf("expect %v, got %v", e, a)
161	}
162}
163
164func TestEncodeEmbeddedPointerStruct(t *testing.T) {
165	type B struct {
166		Bint int
167	}
168	type C struct {
169		Cint int
170	}
171	type A struct {
172		Aint int
173		*B
174		*C
175	}
176	a := A{Aint: 321, B: &B{123}}
177	if e, a := 321, a.Aint; e != a {
178		t.Errorf("expect %v, got %v", e, a)
179	}
180	if e, a := 123, a.Bint; e != a {
181		t.Errorf("expect %v, got %v", e, a)
182	}
183	if a.C != nil {
184		t.Errorf("expect nil, got %v", a.C)
185	}
186
187	actual, err := Marshal(a)
188	if err != nil {
189		t.Errorf("expect nil, got %v", err)
190	}
191	expect := &dynamodb.AttributeValue{
192		M: map[string]*dynamodb.AttributeValue{
193			"Aint": {
194				N: aws.String("321"),
195			},
196			"Bint": {
197				N: aws.String("123"),
198			},
199		},
200	}
201	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
202		t.Errorf("expect %v, got %v", e, a)
203	}
204}
205
206func TestEncodeUnixTime(t *testing.T) {
207	type A struct {
208		Normal time.Time
209		Tagged time.Time `dynamodbav:",unixtime"`
210		Typed  UnixTime
211	}
212
213	a := A{
214		Normal: time.Unix(123, 0).UTC(),
215		Tagged: time.Unix(456, 0),
216		Typed:  UnixTime(time.Unix(789, 0)),
217	}
218
219	actual, err := Marshal(a)
220	if err != nil {
221		t.Errorf("expect nil, got %v", err)
222	}
223	expect := &dynamodb.AttributeValue{
224		M: map[string]*dynamodb.AttributeValue{
225			"Normal": {
226				S: aws.String("1970-01-01T00:02:03Z"),
227			},
228			"Tagged": {
229				N: aws.String("456"),
230			},
231			"Typed": {
232				N: aws.String("789"),
233			},
234		},
235	}
236	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
237		t.Errorf("expect %v, got %v", e, a)
238	}
239}
240
241type AliasedTime time.Time
242
243func TestEncodeAliasedUnixTime(t *testing.T) {
244	type A struct {
245		Normal AliasedTime
246		Tagged AliasedTime `dynamodbav:",unixtime"`
247	}
248
249	a := A{
250		Normal: AliasedTime(time.Unix(123, 0).UTC()),
251		Tagged: AliasedTime(time.Unix(456, 0)),
252	}
253
254	actual, err := Marshal(a)
255	if err != nil {
256		t.Errorf("expect no err, got %v", err)
257	}
258	expect := &dynamodb.AttributeValue{
259		M: map[string]*dynamodb.AttributeValue{
260			"Normal": {
261				S: aws.String("1970-01-01T00:02:03Z"),
262			},
263			"Tagged": {
264				N: aws.String("456"),
265			},
266		},
267	}
268	if e, a := expect, actual; !reflect.DeepEqual(e, a) {
269		t.Errorf("expect %v, got %v", e, a)
270	}
271}
272
273func TestEncoderFieldByIndex(t *testing.T) {
274	type (
275		Middle struct{ Inner int }
276		Outer  struct{ *Middle }
277	)
278
279	// nil embedded struct
280	outer := Outer{}
281	outerFields := unionStructFields(reflect.TypeOf(outer), MarshalOptions{})
282	innerField, _ := outerFields.FieldByName("Inner")
283
284	_, found := encoderFieldByIndex(reflect.ValueOf(&outer).Elem(), innerField.Index)
285	if found != false {
286		t.Error("expected found to be false when embedded struct is nil")
287	}
288
289	// non-nil embedded struct
290	outer = Outer{Middle: &Middle{Inner: 3}}
291	outerFields = unionStructFields(reflect.TypeOf(outer), MarshalOptions{})
292	innerField, _ = outerFields.FieldByName("Inner")
293
294	f, found := encoderFieldByIndex(reflect.ValueOf(&outer).Elem(), innerField.Index)
295	if !found {
296		t.Error("expected found to be true")
297	}
298	if f.Kind() != reflect.Int || f.Int() != int64(outer.Inner) {
299		t.Error("expected f to be of kind Int with value equal to outer.Inner")
300	}
301}
302