1package defaults
2
3import (
4	"reflect"
5	"testing"
6	"time"
7
8	"github.com/creasty/defaults/internal/fixture"
9)
10
11type (
12	MyInt     int
13	MyInt8    int8
14	MyInt16   int16
15	MyInt32   int32
16	MyInt64   int64
17	MyUint    uint
18	MyUint8   uint8
19	MyUint16  uint16
20	MyUint32  uint32
21	MyUint64  uint64
22	MyUintptr uintptr
23	MyFloat32 float32
24	MyFloat64 float64
25	MyBool    bool
26	MyString  string
27	MyMap     map[string]int
28	MySlice   []int
29)
30
31type Sample struct {
32	Int       int           `default:"1"`
33	Int8      int8          `default:"8"`
34	Int16     int16         `default:"16"`
35	Int32     int32         `default:"32"`
36	Int64     int64         `default:"64"`
37	Uint      uint          `default:"1"`
38	Uint8     uint8         `default:"8"`
39	Uint16    uint16        `default:"16"`
40	Uint32    uint32        `default:"32"`
41	Uint64    uint64        `default:"64"`
42	Uintptr   uintptr       `default:"1"`
43	Float32   float32       `default:"1.32"`
44	Float64   float64       `default:"1.64"`
45	BoolTrue  bool          `default:"true"`
46	BoolFalse bool          `default:"false"`
47	String    string        `default:"hello"`
48	Duration  time.Duration `default:"10s"`
49
50	IntOct    int    `default:"0o1"`
51	Int8Oct   int8   `default:"0o10"`
52	Int16Oct  int16  `default:"0o20"`
53	Int32Oct  int32  `default:"0o40"`
54	Int64Oct  int64  `default:"0o100"`
55	UintOct   uint   `default:"0o1"`
56	Uint8Oct  uint8  `default:"0o10"`
57	Uint16Oct uint16 `default:"0o20"`
58	Uint32Oct uint32 `default:"0o40"`
59	Uint64Oct uint64 `default:"0o100"`
60
61	IntHex    int    `default:"0x1"`
62	Int8Hex   int8   `default:"0x8"`
63	Int16Hex  int16  `default:"0x10"`
64	Int32Hex  int32  `default:"0x20"`
65	Int64Hex  int64  `default:"0x40"`
66	UintHex   uint   `default:"0x1"`
67	Uint8Hex  uint8  `default:"0x8"`
68	Uint16Hex uint16 `default:"0x10"`
69	Uint32Hex uint32 `default:"0x20"`
70	Uint64Hex uint64 `default:"0x40"`
71
72	IntBin    int    `default:"0b1"`
73	Int8Bin   int8   `default:"0b1000"`
74	Int16Bin  int16  `default:"0b10000"`
75	Int32Bin  int32  `default:"0b100000"`
76	Int64Bin  int64  `default:"0b1000000"`
77	UintBin   uint   `default:"0b1"`
78	Uint8Bin  uint8  `default:"0b1000"`
79	Uint16Bin uint16 `default:"0b10000"`
80	Uint32Bin uint32 `default:"0b100000"`
81	Uint64Bin uint64 `default:"0b1000000"`
82
83	Struct Struct         `default:"{}"`
84	Map    map[string]int `default:"{}"`
85	Slice  []string       `default:"[]"`
86
87	IntPtr     *int            `default:"1"`
88	UintPtr    *uint           `default:"1"`
89	Float32Ptr *float32        `default:"1"`
90	BoolPtr    *bool           `default:"true"`
91	StringPtr  *string         `default:"hello"`
92	StructPtr  *Struct         `default:"{}"`
93	MapPtr     *map[string]int `default:"{}"`
94	SlicePtr   *[]string       `default:"[]"`
95
96	MyInt       MyInt     `default:"1"`
97	MyInt8      MyInt8    `default:"8"`
98	MyInt16     MyInt16   `default:"16"`
99	MyInt32     MyInt32   `default:"32"`
100	MyInt64     MyInt64   `default:"64"`
101	MyUint      MyUint    `default:"1"`
102	MyUint8     MyUint8   `default:"8"`
103	MyUint16    MyUint16  `default:"16"`
104	MyUint32    MyUint32  `default:"32"`
105	MyUint64    MyUint64  `default:"64"`
106	MyUintptr   MyUintptr `default:"1"`
107	MyFloat32   MyFloat32 `default:"1.32"`
108	MyFloat64   MyFloat64 `default:"1.64"`
109	MyBoolTrue  MyBool    `default:"true"`
110	MyBoolFalse MyBool    `default:"false"`
111	MyString    MyString  `default:"hello"`
112	MyMap       MyMap     `default:"{}"`
113	MySlice     MySlice   `default:"[]"`
114
115	StructWithJSON    Struct         `default:"{\"Foo\": 123}"`
116	StructPtrWithJSON *Struct        `default:"{\"Foo\": 123}"`
117	MapWithJSON       map[string]int `default:"{\"foo\": 123}"`
118	SliceWithJSON     []string       `default:"[\"foo\"]"`
119
120	Empty string `default:""`
121
122	NoDefault       *string `default:"-"`
123	NoDefaultStruct Struct  `default:"-"`
124
125	MapWithNoTag               map[string]int
126	SliceWithNoTag             []string
127	StructPtrWithNoTag         *Struct
128	StructWithNoTag            Struct
129	DeepSliceOfStructWithNoTag [][][]Struct
130
131	NonInitialString    string  `default:"foo"`
132	NonInitialSlice     []int   `default:"[123]"`
133	NonInitialStruct    Struct  `default:"{}"`
134	NonInitialStructPtr *Struct `default:"{}"`
135}
136
137type Struct struct {
138	Embedded `default:"{}"`
139
140	Foo         int
141	Bar         int
142	WithDefault string `default:"foo"`
143}
144
145func (s *Struct) SetDefaults() {
146	s.Bar = 456
147}
148
149type Embedded struct {
150	Int int `default:"1"`
151}
152
153func TestMustSet(t *testing.T) {
154
155	t.Run("right way", func(t *testing.T) {
156		defer func() {
157			if err := recover(); err != nil {
158				t.Fatalf("it should not panic error: %v", err)
159			}
160		}()
161		sample := &Sample{
162			NonInitialString:           "string",
163			NonInitialSlice:            []int{1, 2, 3},
164			NonInitialStruct:           Struct{Foo: 123},
165			NonInitialStructPtr:        &Struct{Foo: 123},
166			DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}},
167		}
168		MustSet(sample)
169	})
170
171	t.Run("not struct", func(t *testing.T) {
172		defer func() {
173			if err := recover(); err != nil {
174				t.Logf("panic error: %v", err)
175			}
176		}()
177		var a int
178		MustSet(&a)
179	})
180
181	t.Run("not pointer", func(t *testing.T) {
182		defer func() {
183			if err := recover(); err != nil {
184				t.Logf("panic error: %v", err)
185			}
186		}()
187		sample := Sample{
188			NonInitialString:           "string",
189			NonInitialSlice:            []int{1, 2, 3},
190			NonInitialStruct:           Struct{Foo: 123},
191			NonInitialStructPtr:        &Struct{Foo: 123},
192			DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}},
193		}
194		MustSet(sample)
195	})
196
197}
198
199func TestInit(t *testing.T) {
200	sample := &Sample{
201		NonInitialString:           "string",
202		NonInitialSlice:            []int{1, 2, 3},
203		NonInitialStruct:           Struct{Foo: 123},
204		NonInitialStructPtr:        &Struct{Foo: 123},
205		DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}},
206	}
207
208	if err := Set(sample); err != nil {
209		t.Fatalf("it should not return an error: %v", err)
210	}
211
212	nonPtrVal := 1
213
214	if err := Set(nonPtrVal); err == nil {
215		t.Fatalf("it should return an error when used for a non-pointer type")
216	}
217	if err := Set(&nonPtrVal); err == nil {
218		t.Fatalf("it should return an error when used for a non-pointer type")
219	}
220
221	Set(&fixture.Sample{}) // should not panic
222
223	t.Run("primitive types", func(t *testing.T) {
224		if sample.Int != 1 {
225			t.Errorf("it should initialize int")
226		}
227		if sample.Int8 != 8 {
228			t.Errorf("it should initialize int8")
229		}
230		if sample.Int16 != 16 {
231			t.Errorf("it should initialize int16")
232		}
233		if sample.Int32 != 32 {
234			t.Errorf("it should initialize int32")
235		}
236		if sample.Int64 != 64 {
237			t.Errorf("it should initialize int64")
238		}
239		if sample.Uint != 1 {
240			t.Errorf("it should initialize uint")
241		}
242		if sample.Uint8 != 8 {
243			t.Errorf("it should initialize uint8")
244		}
245		if sample.Uint16 != 16 {
246			t.Errorf("it should initialize uint16")
247		}
248		if sample.Uint32 != 32 {
249			t.Errorf("it should initialize uint32")
250		}
251		if sample.Uint64 != 64 {
252			t.Errorf("it should initialize uint64")
253		}
254		if sample.Uintptr != 1 {
255			t.Errorf("it should initialize uintptr")
256		}
257		if sample.Float32 != 1.32 {
258			t.Errorf("it should initialize float32")
259		}
260		if sample.Float64 != 1.64 {
261			t.Errorf("it should initialize float64")
262		}
263		if sample.BoolTrue != true {
264			t.Errorf("it should initialize bool (true)")
265		}
266		if sample.BoolFalse != false {
267			t.Errorf("it should initialize bool (false)")
268		}
269		if *sample.BoolPtr != true {
270			t.Errorf("it should initialize bool (true)")
271		}
272		if sample.String != "hello" {
273			t.Errorf("it should initialize string")
274		}
275
276		if sample.IntOct != 0o1 {
277			t.Errorf("it should initialize int with octal literal")
278		}
279		if sample.Int8Oct != 0o10 {
280			t.Errorf("it should initialize int8 with octal literal")
281		}
282		if sample.Int16Oct != 0o20 {
283			t.Errorf("it should initialize int16 with octal literal")
284		}
285		if sample.Int32Oct != 0o40 {
286			t.Errorf("it should initialize int32 with octal literal")
287		}
288		if sample.Int64Oct != 0o100 {
289			t.Errorf("it should initialize int64 with octal literal")
290		}
291		if sample.UintOct != 0o1 {
292			t.Errorf("it should initialize uint with octal literal")
293		}
294		if sample.Uint8Oct != 0o10 {
295			t.Errorf("it should initialize uint8 with octal literal")
296		}
297		if sample.Uint16Oct != 0o20 {
298			t.Errorf("it should initialize uint16 with octal literal")
299		}
300		if sample.Uint32Oct != 0o40 {
301			t.Errorf("it should initialize uint32 with octal literal")
302		}
303		if sample.Uint64Oct != 0o100 {
304			t.Errorf("it should initialize uint64 with octal literal")
305		}
306
307		if sample.IntHex != 0x1 {
308			t.Errorf("it should initialize int with hexadecimal literal")
309		}
310		if sample.Int8Hex != 0x8 {
311			t.Errorf("it should initialize int8 with hexadecimal literal")
312		}
313		if sample.Int16Hex != 0x10 {
314			t.Errorf("it should initialize int16 with hexadecimal literal")
315		}
316		if sample.Int32Hex != 0x20 {
317			t.Errorf("it should initialize int32 with hexadecimal literal")
318		}
319		if sample.Int64Hex != 0x40 {
320			t.Errorf("it should initialize int64 with hexadecimal literal")
321		}
322		if sample.UintHex != 0x1 {
323			t.Errorf("it should initialize uint with hexadecimal literal")
324		}
325		if sample.Uint8Hex != 0x8 {
326			t.Errorf("it should initialize uint8 with hexadecimal literal")
327		}
328		if sample.Uint16Hex != 0x10 {
329			t.Errorf("it should initialize uint16 with hexadecimal literal")
330		}
331		if sample.Uint32Hex != 0x20 {
332			t.Errorf("it should initialize uint32 with hexadecimal literal")
333		}
334		if sample.Uint64Hex != 0x40 {
335			t.Errorf("it should initialize uint64 with hexadecimal literal")
336		}
337
338		if sample.IntBin != 0b1 {
339			t.Errorf("it should initialize int with binary literal")
340		}
341		if sample.Int8Bin != 0b1000 {
342			t.Errorf("it should initialize int8 with binary literal")
343		}
344		if sample.Int16Bin != 0b10000 {
345			t.Errorf("it should initialize int16 with binary literal")
346		}
347		if sample.Int32Bin != 0b100000 {
348			t.Errorf("it should initialize int32 with binary literal")
349		}
350		if sample.Int64Bin != 0b1000000 {
351			t.Errorf("it should initialize int64 with binary literal")
352		}
353		if sample.UintBin != 0b1 {
354			t.Errorf("it should initialize uint with binary literal")
355		}
356		if sample.Uint8Bin != 0b1000 {
357			t.Errorf("it should initialize uint8 with binary literal")
358		}
359		if sample.Uint16Bin != 0b10000 {
360			t.Errorf("it should initialize uint16 with binary literal")
361		}
362		if sample.Uint32Bin != 0b100000 {
363			t.Errorf("it should initialize uint32 with binary literal")
364		}
365		if sample.Uint64Bin != 0b1000000 {
366			t.Errorf("it should initialize uint64 with binary literal")
367		}
368	})
369
370	t.Run("complex types", func(t *testing.T) {
371		if sample.StructPtr == nil {
372			t.Errorf("it should initialize struct pointer")
373		}
374		if sample.Map == nil {
375			t.Errorf("it should initialize map")
376		}
377		if sample.Slice == nil {
378			t.Errorf("it should initialize slice")
379		}
380	})
381
382	t.Run("pointer types", func(t *testing.T) {
383		if sample.IntPtr == nil || *sample.IntPtr != 1 {
384			t.Errorf("it should initialize int pointer")
385		}
386		if sample.UintPtr == nil || *sample.UintPtr != 1 {
387			t.Errorf("it should initialize uint pointer")
388		}
389		if sample.Float32Ptr == nil || *sample.Float32Ptr != 1 {
390			t.Errorf("it should initialize float32 pointer")
391		}
392		if sample.BoolPtr == nil || *sample.BoolPtr != true {
393			t.Errorf("it should initialize bool pointer")
394		}
395		if sample.StringPtr == nil || *sample.StringPtr != "hello" {
396			t.Errorf("it should initialize string pointer")
397		}
398		if sample.MapPtr == nil {
399			t.Errorf("it should initialize map ptr")
400		}
401		if sample.SlicePtr == nil {
402			t.Errorf("it should initialize slice ptr")
403		}
404	})
405
406	t.Run("aliased types", func(t *testing.T) {
407		if sample.MyInt != 1 {
408			t.Errorf("it should initialize int")
409		}
410		if sample.MyInt8 != 8 {
411			t.Errorf("it should initialize int8")
412		}
413		if sample.MyInt16 != 16 {
414			t.Errorf("it should initialize int16")
415		}
416		if sample.MyInt32 != 32 {
417			t.Errorf("it should initialize int32")
418		}
419		if sample.MyInt64 != 64 {
420			t.Errorf("it should initialize int64")
421		}
422		if sample.MyUint != 1 {
423			t.Errorf("it should initialize uint")
424		}
425		if sample.MyUint8 != 8 {
426			t.Errorf("it should initialize uint8")
427		}
428		if sample.MyUint16 != 16 {
429			t.Errorf("it should initialize uint16")
430		}
431		if sample.MyUint32 != 32 {
432			t.Errorf("it should initialize uint32")
433		}
434		if sample.MyUint64 != 64 {
435			t.Errorf("it should initialize uint64")
436		}
437		if sample.MyUintptr != 1 {
438			t.Errorf("it should initialize uintptr")
439		}
440		if sample.MyFloat32 != 1.32 {
441			t.Errorf("it should initialize float32")
442		}
443		if sample.MyFloat64 != 1.64 {
444			t.Errorf("it should initialize float64")
445		}
446		if sample.MyBoolTrue != true {
447			t.Errorf("it should initialize bool (true)")
448		}
449		if sample.MyBoolFalse != false {
450			t.Errorf("it should initialize bool (false)")
451		}
452		if sample.MyString != "hello" {
453			t.Errorf("it should initialize string")
454		}
455
456		if sample.MyMap == nil {
457			t.Errorf("it should initialize map")
458		}
459		if sample.MySlice == nil {
460			t.Errorf("it should initialize slice")
461		}
462	})
463
464	t.Run("nested", func(t *testing.T) {
465		if sample.Struct.WithDefault != "foo" {
466			t.Errorf("it should set default on inner field in struct")
467		}
468		if sample.StructPtr == nil || sample.StructPtr.WithDefault != "foo" {
469			t.Errorf("it should set default on inner field in struct pointer")
470		}
471		if sample.Struct.Embedded.Int != 1 {
472			t.Errorf("it should set default on an Embedded struct")
473		}
474	})
475
476	t.Run("complex types with json", func(t *testing.T) {
477		if sample.StructWithJSON.Foo != 123 {
478			t.Errorf("it should initialize struct with json")
479		}
480		if sample.StructPtrWithJSON == nil || sample.StructPtrWithJSON.Foo != 123 {
481			t.Errorf("it should initialize struct pointer with json")
482		}
483		if sample.MapWithJSON["foo"] != 123 {
484			t.Errorf("it should initialize map with json")
485		}
486		if len(sample.SliceWithJSON) == 0 || sample.SliceWithJSON[0] != "foo" {
487			t.Errorf("it should initialize slice with json")
488		}
489
490		t.Run("invalid json", func(t *testing.T) {
491			if err := Set(&struct {
492				I []int `default:"[!]"`
493			}{}); err == nil {
494				t.Errorf("it should return error")
495			}
496
497			if err := Set(&struct {
498				I map[string]int `default:"{1}"`
499			}{}); err == nil {
500				t.Errorf("it should return error")
501			}
502
503			if err := Set(&struct {
504				S struct {
505					I []int
506				} `default:"{!}"`
507			}{}); err == nil {
508				t.Errorf("it should return error")
509			}
510
511			if err := Set(&struct {
512				S struct {
513					I []int `default:"[!]"`
514				}
515			}{}); err == nil {
516				t.Errorf("it should return error")
517			}
518		})
519	})
520
521	t.Run("Setter interface", func(t *testing.T) {
522		if sample.Struct.Bar != 456 {
523			t.Errorf("it should initialize struct")
524		}
525		if sample.StructPtr == nil || sample.StructPtr.Bar != 456 {
526			t.Errorf("it should initialize struct pointer")
527		}
528	})
529
530	t.Run("non-initial value", func(t *testing.T) {
531		if sample.NonInitialString != "string" {
532			t.Errorf("it should not override non-initial value")
533		}
534		if !reflect.DeepEqual(sample.NonInitialSlice, []int{1, 2, 3}) {
535			t.Errorf("it should not override non-initial value")
536		}
537		if !reflect.DeepEqual(sample.NonInitialStruct, Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) {
538			t.Errorf("it should not override non-initial value but set defaults for fields")
539		}
540		if !reflect.DeepEqual(sample.NonInitialStructPtr, &Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) {
541			t.Errorf("it should not override non-initial value but set defaults for fields")
542		}
543	})
544
545	t.Run("no tag", func(t *testing.T) {
546		if sample.MapWithNoTag != nil {
547			t.Errorf("it should not initialize pointer type (map)")
548		}
549		if sample.SliceWithNoTag != nil {
550			t.Errorf("it should not initialize pointer type (slice)")
551		}
552		if sample.StructPtrWithNoTag != nil {
553			t.Errorf("it should not initialize pointer type (struct)")
554		}
555		if sample.StructWithNoTag.WithDefault != "foo" {
556			t.Errorf("it should automatically recurse into a struct even without a tag")
557		}
558		if !reflect.DeepEqual(sample.DeepSliceOfStructWithNoTag, [][][]Struct{{{{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}}}}) {
559			t.Errorf("it should automatically recurse into a slice of structs even without a tag")
560		}
561	})
562
563	t.Run("opt-out", func(t *testing.T) {
564		if sample.NoDefault != nil {
565			t.Errorf("it should not be set")
566		}
567		if sample.NoDefaultStruct.WithDefault != "" {
568			t.Errorf("it should not initialize a struct with default values")
569		}
570	})
571}
572
573func TestCanUpdate(t *testing.T) {
574	type st struct{ Int int }
575
576	var myStructPtr *st
577
578	pairs := map[interface{}]bool{
579		0:            true,
580		123:          false,
581		float64(0):   true,
582		float64(123): false,
583		"":           true,
584		"string":     false,
585		false:        true,
586		true:         false,
587		st{}:         true,
588		st{Int: 123}: false,
589		myStructPtr:  true,
590		&st{}:        false,
591	}
592	for input, expect := range pairs {
593		output := CanUpdate(input)
594		if output != expect {
595			t.Errorf("CanUpdate(%v) returns %v, expected %v", input, output, expect)
596		}
597	}
598}
599
600type Child struct {
601	Name string `default:"Tom"`
602	Age  int    `default:"20"`
603}
604
605type Parent struct {
606	Child *Child
607}
608
609func TestPointerStructMember(t *testing.T) {
610	m := Parent{Child: &Child{Name: "Jim"}}
611	Set(&m)
612	if m.Child.Age != 20 {
613		t.Errorf("20 is expected")
614	}
615}
616
617type Main struct {
618	MainInt int `default:"-"`
619	*Other  `default:"{}"`
620}
621
622type Other struct {
623	OtherInt int `default:"-"`
624}
625
626func (s *Main) SetDefaults() {
627	if CanUpdate(s.MainInt) {
628		s.MainInt = 1
629	}
630}
631
632func (s *Other) SetDefaults() {
633	if CanUpdate(s.OtherInt) {
634		s.OtherInt = 1
635	}
636}
637
638func TestDefaultsSetter(t *testing.T) {
639	main := &Main{}
640	Set(main)
641	if main.OtherInt != 1 {
642		t.Errorf("expected 1 for OtherInt, got %d", main.OtherInt)
643	}
644	if main.MainInt != 1 {
645		t.Errorf("expected 1 for MainInt, got %d", main.MainInt)
646	}
647}
648