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