1package funk
2
3import (
4	"database/sql"
5	"fmt"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9)
10
11func TestSet_EmptyPath(t *testing.T) {
12	// it is supposed to change the var passed in
13	var testCases = []struct {
14		// will use path = ""
15		Original interface{}
16		SetVal   interface{}
17	}{
18		// int
19		{
20			Original: 100,
21			SetVal:   1,
22		},
23		// string
24		{
25			Original: "",
26			SetVal:   "val",
27		},
28		// struct
29		{
30			Original: Bar{Name: "bar"},
31			SetVal:   Bar{Name: "val"},
32		},
33		// slice
34		{
35			Original: []Bar{{Name: "bar"}},
36			SetVal:   []Bar{{Name: "val1"}, {Name: "val2"}},
37		},
38	}
39
40	for idx, tc := range testCases {
41		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
42			is := assert.New(t)
43			// use empty path
44			// must take the addr of the variable to be set
45			err := Set(&tc.Original, tc.SetVal, "")
46			is.NoError(err)
47			is.Equal(tc.Original, tc.SetVal) // original should be set to SetVal
48		})
49	}
50}
51
52func TestSet_StructBasicOneLevel(t *testing.T) {
53	is := assert.New(t)
54	// we set field one by one of baz with expected value
55	baz := Foo{
56		ID:        100,
57		FirstName: "firstname",
58		LastName:  "lastname",
59		Age:       23,
60		Bar:       &Bar{Name: "bar"},
61		Bars:      []*Bar{{Name: "1"}},
62		EmptyValue: sql.NullInt64{
63			Int64: 64,
64			Valid: false,
65		},
66	}
67	expected := Foo{
68		ID:        1,
69		FirstName: "firstname1",
70		LastName:  "lastname1",
71		Age:       24,
72		Bar:       &Bar{Name: "b1", Bar: &Bar{Name: "b2"}},
73		Bars:      []*Bar{{Name: "1"}, {Name: "2"}},
74		EmptyValue: sql.NullInt64{
75			Int64: 11,
76			Valid: true,
77		},
78	}
79	err := Set(&baz, 1, "ID")
80	is.NoError(err)
81	err = Set(&baz, expected.FirstName, "FirstName")
82	is.NoError(err)
83	err = Set(&baz, expected.LastName, "LastName")
84	is.NoError(err)
85	err = Set(&baz, expected.Age, "Age")
86	is.NoError(err)
87	err = Set(&baz, expected.Bar, "Bar")
88	is.NoError(err)
89	err = Set(&baz, expected.Bars, "Bars")
90	is.NoError(err)
91	err = Set(&baz, expected.EmptyValue, "EmptyValue")
92	is.NoError(err)
93	is.Equal(baz, expected)
94}
95
96func TestSetStruct_MultiLevels(t *testing.T) {
97
98	var testCases = []struct {
99		Original Bar
100		Path     string
101		SetVal   interface{}
102		Expected Bar
103	}{
104		// Set slice in 4th level
105		{
106			Original: Bar{
107				Name: "1", // name indicates level
108				Bar: &Bar{
109					Name: "2",
110					Bars: []*Bar{
111						{Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}},
112						{Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}},
113					},
114				},
115			},
116			Path:   "Bar.Bars.Bars.Name",
117			SetVal: "val",
118			Expected: Bar{
119				Name: "1",
120				Bar: &Bar{
121					Name: "2",
122					Bars: []*Bar{
123						{Name: "3-1", Bars: []*Bar{{Name: "val"}, {Name: "val"}, {Name: "val"}}},
124						{Name: "3-2", Bars: []*Bar{{Name: "val"}, {Name: "val"}}},
125					},
126				},
127			},
128		},
129		// Set multilevel uninitialized ptr
130		{
131			Original: Bar{
132				Name: "1", // name indicates level
133				Bar:  nil,
134			},
135			Path:   "Bar.Bar.Bar.Name",
136			SetVal: "val",
137			Expected: Bar{
138				Name: "1",
139				Bar: &Bar{
140					Name: "", // level 2
141					Bar: &Bar{
142						Bar: &Bar{
143							Name: "val", //level 3
144						},
145					},
146				},
147			},
148		},
149		// mix of uninitialized ptr and slices
150		{
151			Original: Bar{
152				Name: "1", // name indicates level
153				Bar: &Bar{
154					Name: "2",
155					Bars: []*Bar{
156						{Name: "3-1", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}, {Name: "4-3"}}},
157						{Name: "3-2", Bars: []*Bar{{Name: "4-1"}, {Name: "4-2"}}},
158					},
159				},
160			},
161			Path:   "Bar.Bars.Bars.Bar.Name",
162			SetVal: "val",
163			Expected: Bar{
164				Name: "1", // name indicates level
165				Bar: &Bar{
166					Name: "2",
167					Bars: []*Bar{
168						{Name: "3-1", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}},
169							{Name: "4-2", Bar: &Bar{Name: "val"}}, {Name: "4-3", Bar: &Bar{Name: "val"}}}},
170						{Name: "3-2", Bars: []*Bar{{Name: "4-1", Bar: &Bar{Name: "val"}}, {Name: "4-2", Bar: &Bar{Name: "val"}}}},
171					},
172				},
173			},
174		},
175	}
176
177	for idx, tc := range testCases {
178		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
179			is := assert.New(t)
180			// take the addr and then pass it in
181			err := Set(&tc.Original, tc.SetVal, tc.Path)
182			is.NoError(err)
183			is.Equal(tc.Expected, tc.Original)
184		})
185	}
186}
187
188func TestSet_StructWithCyclicStruct(t *testing.T) {
189	is := assert.New(t)
190
191	testBar := Bar{
192		Name: "testBar",
193		Bar:  nil,
194	}
195	testBar.Bar = &testBar
196
197	err := Set(&testBar, "val", "Bar.Bar.Name")
198	is.NoError(err)
199	is.Equal("val", testBar.Name)
200}
201
202func TestSet_StructWithFieldNotInitialized(t *testing.T) {
203	is := assert.New(t)
204	myFoo := &Foo{
205		Bar: nil, // we will try to set bar's field
206	}
207	err := Set(myFoo, "name", "Bar.Name")
208	is.NoError(err)
209	is.Equal("name", myFoo.Bar.Name)
210}
211
212func TestSet_SlicePassByPtr(t *testing.T) {
213
214	var testCases = []struct {
215		Original interface{} // slice or array
216		Path     string
217		SetVal   interface{}
218		Expected interface{}
219	}{
220		// Set Slice itself
221		{
222			Original: []*Bar{},
223			Path:     "", // empty path means set the passed in ptr itself
224			SetVal:   []*Bar{{Name: "bar"}},
225			Expected: []*Bar{{Name: "bar"}},
226		},
227		// empty slice
228		{
229			Original: []*Bar{},
230			Path:     "Name",
231			SetVal:   "val",
232			Expected: []*Bar{},
233		},
234		// slice of ptr
235		{
236			Original: []*Bar{{Name: "a"}, {Name: "b"}},
237			Path:     "Name",
238			SetVal:   "val",
239			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
240		},
241		// slice of struct
242		{
243			Original: []Bar{{Name: "a"}, {Name: "b"}},
244			Path:     "Name",
245			SetVal:   "val",
246			Expected: []Bar{{Name: "val"}, {Name: "val"}},
247		},
248		// slice of empty ptr
249		{
250			Original: []*Bar{nil, nil},
251			Path:     "Name",
252			SetVal:   "val",
253			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
254		},
255		// mix of init ptr and nil ptr
256		{
257			Original: []*Bar{{Name: "bar"}, nil},
258			Path:     "Name",
259			SetVal:   "val",
260			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
261		},
262	}
263
264	for idx, tc := range testCases {
265		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
266			is := assert.New(t)
267			// take the addr and then pass it in
268			err := Set(&tc.Original, tc.SetVal, tc.Path)
269			is.NoError(err)
270			is.Equal(tc.Expected, tc.Original)
271		})
272	}
273}
274
275func TestSet_SlicePassDirectly(t *testing.T) {
276	var testCases = []struct {
277		Original interface{} // slice or array
278		Path     string
279		SetVal   interface{}
280		Expected interface{}
281	}{
282		// Set Slice itself does not work here since not passing by ptr
283
284		// empty slice
285		{
286			Original: []*Bar{},
287			Path:     "Name",
288			SetVal:   "val",
289			Expected: []*Bar{},
290		},
291		// slice of ptr
292		{
293			Original: []*Bar{{Name: "a"}, {Name: "b"}},
294			Path:     "Name",
295			SetVal:   "val",
296			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
297		},
298		// slice of struct
299		{
300			Original: []Bar{{Name: "a"}, {Name: "b"}},
301			Path:     "Name",
302			SetVal:   "val",
303			Expected: []Bar{{Name: "val"}, {Name: "val"}},
304		},
305		// slice of empty ptr
306		{
307			Original: []*Bar{nil, nil},
308			Path:     "Name",
309			SetVal:   "val",
310			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
311		},
312		// mix of init ptr and nil ptr
313		{
314			Original: []*Bar{{Name: "bar"}, nil},
315			Path:     "Name",
316			SetVal:   "val",
317			Expected: []*Bar{{Name: "val"}, {Name: "val"}},
318		},
319	}
320
321	for idx, tc := range testCases {
322		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
323			is := assert.New(t)
324			// Not take ptr, pass directly
325			err := Set(tc.Original, tc.SetVal, tc.Path)
326			is.NoError(err)
327			is.Equal(tc.Expected, tc.Original)
328		})
329	}
330}
331
332func TestInterface(t *testing.T) {
333
334	var testCases = []struct {
335		OriginalFoo Foo
336		Path        string
337		SetVal      interface{}
338		ExpectedFoo Foo
339	}{
340		// set string field
341		{
342			Foo{FirstName: ""},
343			"FirstName",
344			"hi",
345			Foo{FirstName: "hi"},
346		},
347		// set interface{} field
348		{
349			Foo{FirstName: "", GeneralInterface: nil},
350			"GeneralInterface",
351			"str",
352			Foo{FirstName: "", GeneralInterface: "str"},
353		},
354		// set field of the interface{} field
355		// Note: set uninitialized interface{} should fail
356		// Note: interface of struct (not ptr to struct) should fail
357		{
358			Foo{FirstName: "", GeneralInterface: &Foo{FirstName: ""}}, // if Foo is not ptr this will fail
359			"GeneralInterface.FirstName",
360			"foo",
361			Foo{FirstName: "", GeneralInterface: &Foo{FirstName: "foo"}},
362		},
363		// interface two level
364		{
365			Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: nil}},
366			"GeneralInterface.GeneralInterface",
367			"val",
368			Foo{FirstName: "", GeneralInterface: &Foo{GeneralInterface: "val"}},
369		},
370	}
371
372	for idx, tc := range testCases {
373		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
374			is := assert.New(t)
375
376			err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path)
377			is.NoError(err)
378			is.Equal(tc.ExpectedFoo, tc.OriginalFoo)
379		})
380	}
381
382}
383
384func TestSet_ErrorCaces(t *testing.T) {
385
386	var testCases = []struct {
387		OriginalFoo Foo
388		Path        string
389		SetVal      interface{}
390	}{
391		// uninit interface
392		// Itf is not initialized so Set cannot properly allocate type
393		{
394			Foo{BarInterface: nil},
395			"BarInterface.Name",
396			"val",
397		},
398		{
399			Foo{GeneralInterface: &Foo{BarInterface: nil}},
400			"GeneralInterface.BarInterface.Name",
401			"val",
402		},
403		// type mismatch
404		{
405			Foo{FirstName: ""},
406			"FirstName",
407			20,
408		},
409	}
410
411	for idx, tc := range testCases {
412		t.Run(fmt.Sprintf("test case #%d", idx+1), func(t *testing.T) {
413			is := assert.New(t)
414
415			err := Set(&tc.OriginalFoo, tc.SetVal, tc.Path)
416			is.Error(err)
417		})
418	}
419
420	t.Run("not pointer", func(t *testing.T) {
421		is := assert.New(t)
422		baz := Bar{Name: "dummy"}
423		err := Set(baz, Bar{Name: "dummy2"}, "Name")
424		is.Error(err)
425	})
426
427	t.Run("Unexported field", func(t *testing.T) {
428		is := assert.New(t)
429		s := struct {
430			name string
431		}{name: "dummy"}
432		err := Set(&s, s, "name")
433		is.Error(err)
434	})
435}
436
437func TestMustSet_Basic(t *testing.T) {
438	t.Run("Variable", func(t *testing.T) {
439		is := assert.New(t)
440		s := 1
441		MustSet(&s, 2, "")
442		is.Equal(2, s)
443	})
444
445	t.Run("Struct", func(t *testing.T) {
446		is := assert.New(t)
447		s := Bar{Name: "a"}
448		MustSet(&s, "b", "Name")
449		is.Equal("b", s.Name)
450	})
451}
452
453// Examples
454
455func ExampleSet() {
456
457	var bar Bar = Bar{
458		Name: "level-0",
459		Bar: &Bar{
460			Name: "level-1",
461			Bars: []*Bar{
462				{Name: "level2-1"},
463				{Name: "level2-2"},
464			},
465		},
466	}
467
468	_ = Set(&bar, "level-0-new", "Name")
469	fmt.Println(bar.Name)
470
471	// discard error use MustSet
472	MustSet(&bar, "level-1-new", "Bar.Name")
473	fmt.Println(bar.Bar.Name)
474
475	_ = Set(&bar, "level-2-new", "Bar.Bars.Name")
476	fmt.Println(bar.Bar.Bars[0].Name)
477	fmt.Println(bar.Bar.Bars[1].Name)
478
479	// Output:
480	// level-0-new
481	// level-1-new
482	// level-2-new
483	// level-2-new
484}
485