1// Copyright 2016 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package fields
16
17import (
18	"encoding/json"
19	"errors"
20	"fmt"
21	"reflect"
22	"testing"
23	"time"
24
25	"cloud.google.com/go/internal/testutil"
26	"github.com/google/go-cmp/cmp"
27)
28
29type embed1 struct {
30	Em1    int
31	Dup    int // annihilates with embed2.Dup
32	Shadow int
33	embed3
34}
35
36type embed2 struct {
37	Dup int
38	embed3
39	embed4
40}
41
42type embed3 struct {
43	Em3 int // annihilated because embed3 is in both embed1 and embed2
44	embed5
45}
46
47type embed4 struct {
48	Em4     int
49	Dup     int // annihilation of Dup in embed1, embed2 hides this Dup
50	*embed1     // ignored because it occurs at a higher level
51}
52
53type embed5 struct {
54	x int
55}
56
57type Anonymous int
58
59type S1 struct {
60	Exported   int
61	unexported int
62	Shadow     int // shadows S1.Shadow
63	embed1
64	*embed2
65	Anonymous
66}
67
68type Time struct {
69	time.Time
70}
71
72var intType = reflect.TypeOf(int(0))
73
74func field(name string, tval interface{}, index ...int) *Field {
75	return &Field{
76		Name:      name,
77		Type:      reflect.TypeOf(tval),
78		Index:     index,
79		ParsedTag: []string(nil),
80	}
81}
82
83func tfield(name string, tval interface{}, index ...int) *Field {
84	return &Field{
85		Name:        name,
86		Type:        reflect.TypeOf(tval),
87		Index:       index,
88		NameFromTag: true,
89		ParsedTag:   []string(nil),
90	}
91}
92
93func TestFieldsNoTags(t *testing.T) {
94	c := NewCache(nil, nil, nil)
95	got, err := c.Fields(reflect.TypeOf(S1{}))
96	if err != nil {
97		t.Fatal(err)
98	}
99	want := []*Field{
100		field("Exported", int(0), 0),
101		field("Shadow", int(0), 2),
102		field("Em1", int(0), 3, 0),
103		field("Em4", int(0), 4, 2, 0),
104		field("Anonymous", Anonymous(0), 5),
105	}
106	for _, f := range want {
107		f.ParsedTag = nil
108	}
109	if msg, ok := compareFields(got, want); !ok {
110		t.Error(msg)
111	}
112}
113
114func TestAgainstJSONEncodingNoTags(t *testing.T) {
115	// Demonstrates that this package produces the same set of fields as encoding/json.
116	s1 := S1{
117		Exported:   1,
118		unexported: 2,
119		Shadow:     3,
120		embed1: embed1{
121			Em1:    4,
122			Dup:    5,
123			Shadow: 6,
124			embed3: embed3{
125				Em3:    7,
126				embed5: embed5{x: 8},
127			},
128		},
129		embed2: &embed2{
130			Dup: 9,
131			embed3: embed3{
132				Em3:    10,
133				embed5: embed5{x: 11},
134			},
135			embed4: embed4{
136				Em4:    12,
137				Dup:    13,
138				embed1: &embed1{Em1: 14},
139			},
140		},
141		Anonymous: Anonymous(15),
142	}
143	var want S1
144	want.embed2 = &embed2{} // need this because reflection won't create it
145	jsonRoundTrip(t, s1, &want)
146	var got S1
147	got.embed2 = &embed2{}
148	fields, err := NewCache(nil, nil, nil).Fields(reflect.TypeOf(got))
149	if err != nil {
150		t.Fatal(err)
151	}
152	setFields(fields, &got, s1)
153	if !testutil.Equal(got, want,
154		cmp.AllowUnexported(S1{}, embed1{}, embed2{}, embed3{}, embed4{}, embed5{})) {
155		t.Errorf("got\n%+v\nwant\n%+v", got, want)
156	}
157}
158
159// Tests use of LeafTypes parameter to NewCache
160func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) {
161	timeLeafFn := func(t reflect.Type) bool {
162		return t == reflect.TypeOf(time.Time{})
163	}
164	// Demonstrates that this package can produce the same set of
165	// fields as encoding/json for a struct with an embedded time.Time.
166	now := time.Now().UTC()
167	myt := Time{
168		now,
169	}
170	var want Time
171	jsonRoundTrip(t, myt, &want)
172	var got Time
173	fields, err := NewCache(nil, nil, timeLeafFn).Fields(reflect.TypeOf(got))
174	if err != nil {
175		t.Fatal(err)
176	}
177	setFields(fields, &got, myt)
178	if !testutil.Equal(got, want) {
179		t.Errorf("got\n%+v\nwant\n%+v", got, want)
180	}
181}
182
183type S2 struct {
184	NoTag     int
185	XXX       int           `json:"tag"` // tag name takes precedence
186	Anonymous `json:"anon"` // anonymous non-structs also get their name from the tag
187	Embed     `json:"em"`   // embedded structs with tags become fields
188	Tag       int
189	YYY       int `json:"Tag"` // tag takes precedence over untagged field of the same name
190	Empty     int `json:""`    // empty tag is noop
191	tEmbed1
192	tEmbed2
193}
194
195type Embed struct {
196	Em int
197}
198
199type tEmbed1 struct {
200	Dup int
201	X   int `json:"Dup2"`
202}
203
204type tEmbed2 struct {
205	Y int `json:"Dup"`  // takes precedence over tEmbed1.Dup because it is tagged
206	Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored
207}
208
209func jsonTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
210	return ParseStandardTag("json", t)
211}
212
213func validateFunc(t reflect.Type) (err error) {
214	if t.Kind() != reflect.Struct {
215		return errors.New("non-struct type used")
216	}
217
218	for i := 0; i < t.NumField(); i++ {
219		if t.Field(i).Type.Kind() == reflect.Slice {
220			return fmt.Errorf("slice field found at field %s on struct %s", t.Field(i).Name, t.Name())
221		}
222	}
223
224	return nil
225}
226
227func TestFieldsWithTags(t *testing.T) {
228	got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
229	if err != nil {
230		t.Fatal(err)
231	}
232	want := []*Field{
233		field("NoTag", int(0), 0),
234		tfield("tag", int(0), 1),
235		tfield("anon", Anonymous(0), 2),
236		tfield("em", Embed{}, 4),
237		tfield("Tag", int(0), 6),
238		field("Empty", int(0), 7),
239		tfield("Dup", int(0), 8, 0),
240	}
241	if msg, ok := compareFields(got, want); !ok {
242		t.Error(msg)
243	}
244}
245
246func TestAgainstJSONEncodingWithTags(t *testing.T) {
247	// Demonstrates that this package produces the same set of fields as encoding/json.
248	s2 := S2{
249		NoTag:     1,
250		XXX:       2,
251		Anonymous: 3,
252		Embed: Embed{
253			Em: 4,
254		},
255		tEmbed1: tEmbed1{
256			Dup: 5,
257			X:   6,
258		},
259		tEmbed2: tEmbed2{
260			Y: 7,
261			Z: 8,
262		},
263	}
264	var want S2
265	jsonRoundTrip(t, s2, &want)
266	var got S2
267	fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(got))
268	if err != nil {
269		t.Fatal(err)
270	}
271	setFields(fields, &got, s2)
272	if !testutil.Equal(got, want, cmp.AllowUnexported(S2{})) {
273		t.Errorf("got\n%+v\nwant\n%+v", got, want)
274	}
275}
276
277func TestUnexportedAnonymousNonStruct(t *testing.T) {
278	// An unexported anonymous non-struct field should not be recorded.
279	// This is currently a bug in encoding/json.
280	// https://github.com/golang/go/issues/18009
281	type S struct{}
282
283	got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
284	if err != nil {
285		t.Fatal(err)
286	}
287	if len(got) != 0 {
288		t.Errorf("got %d fields, want 0", len(got))
289	}
290}
291
292func TestUnexportedAnonymousStruct(t *testing.T) {
293	// An unexported anonymous struct with a tag is ignored.
294	// This is currently a bug in encoding/json.
295	// https://github.com/golang/go/issues/18009
296	type (
297		s1 struct{ X int }
298		S2 struct {
299			s1 `json:"Y"`
300		}
301	)
302	got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
303	if err != nil {
304		t.Fatal(err)
305	}
306	if len(got) != 0 {
307		t.Errorf("got %d fields, want 0", len(got))
308	}
309}
310
311func TestDominantField(t *testing.T) {
312	// With fields sorted by index length and then by tag presence,
313	// the dominant field is always the first. Make sure all error
314	// cases are caught.
315	for _, test := range []struct {
316		fields []Field
317		wantOK bool
318	}{
319		// A single field is OK.
320		{[]Field{{Index: []int{0}}}, true},
321		{[]Field{{Index: []int{0}, NameFromTag: true}}, true},
322		// A single field at top level is OK.
323		{[]Field{{Index: []int{0}}, {Index: []int{1, 0}}}, true},
324		{[]Field{{Index: []int{0}}, {Index: []int{1, 0}, NameFromTag: true}}, true},
325		{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1, 0}, NameFromTag: true}}, true},
326		// A single tagged field is OK.
327		{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true},
328		// Two untagged fields at the same level is an error.
329		{[]Field{{Index: []int{0}}, {Index: []int{1}}}, false},
330		// Two tagged fields at the same level is an error.
331		{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}, NameFromTag: true}}, false},
332	} {
333		_, gotOK := dominantField(test.fields)
334		if gotOK != test.wantOK {
335			t.Errorf("%v: got %t, want %t", test.fields, gotOK, test.wantOK)
336		}
337	}
338}
339
340func TestIgnore(t *testing.T) {
341	type S struct {
342		X int `json:"-"`
343	}
344	got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
345	if err != nil {
346		t.Fatal(err)
347	}
348	if len(got) != 0 {
349		t.Errorf("got %d fields, want 0", len(got))
350	}
351}
352
353func TestParsedTag(t *testing.T) {
354	type S struct {
355		X int `json:"name,omitempty"`
356	}
357	got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
358	if err != nil {
359		t.Fatal(err)
360	}
361	want := []*Field{
362		{Name: "name", NameFromTag: true, Type: intType,
363			Index: []int{0}, ParsedTag: []string{"omitempty"}},
364	}
365	if msg, ok := compareFields(got, want); !ok {
366		t.Error(msg)
367	}
368}
369
370func TestValidateFunc(t *testing.T) {
371	type MyInvalidStruct struct {
372		A string
373		B []int
374	}
375
376	_, err := NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyInvalidStruct{}))
377	if err == nil {
378		t.Fatal("expected error, got nil")
379	}
380
381	type MyValidStruct struct {
382		A string
383		B int
384	}
385	_, err = NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyValidStruct{}))
386	if err != nil {
387		t.Fatalf("expected nil, got error: %s\n", err)
388	}
389}
390
391func compareFields(got []Field, want []*Field) (msg string, ok bool) {
392	if len(got) != len(want) {
393		return fmt.Sprintf("got %d fields, want %d", len(got), len(want)), false
394	}
395	for i, g := range got {
396		w := *want[i]
397		if !fieldsEqual(&g, &w) {
398			return fmt.Sprintf("got\n%+v\nwant\n%+v", g, w), false
399		}
400	}
401	return "", true
402}
403
404// Need this because Field contains a function, which cannot be compared even
405// by testutil.Equal.
406func fieldsEqual(f1, f2 *Field) bool {
407	if f1 == nil || f2 == nil {
408		return f1 == f2
409	}
410	return f1.Name == f2.Name &&
411		f1.NameFromTag == f2.NameFromTag &&
412		f1.Type == f2.Type &&
413		testutil.Equal(f1.ParsedTag, f2.ParsedTag)
414}
415
416// Set the fields of dst from those of src.
417// dst must be a pointer to a struct value.
418// src must be a struct value.
419func setFields(fields []Field, dst, src interface{}) {
420	vsrc := reflect.ValueOf(src)
421	vdst := reflect.ValueOf(dst).Elem()
422	for _, f := range fields {
423		fdst := vdst.FieldByIndex(f.Index)
424		fsrc := vsrc.FieldByIndex(f.Index)
425		fdst.Set(fsrc)
426	}
427}
428
429func jsonRoundTrip(t *testing.T, in, out interface{}) {
430	bytes, err := json.Marshal(in)
431	if err != nil {
432		t.Fatal(err)
433	}
434	if err := json.Unmarshal(bytes, out); err != nil {
435		t.Fatal(err)
436	}
437}
438
439type S3 struct {
440	S4
441	Abc        int
442	AbC        int
443	Tag        int
444	X          int `json:"Tag"`
445	unexported int
446}
447
448type S4 struct {
449	ABc int
450	Y   int `json:"Abc"` // ignored because of top-level Abc
451}
452
453func TestMatchingField(t *testing.T) {
454	fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
455	if err != nil {
456		t.Fatal(err)
457	}
458	for _, test := range []struct {
459		name string
460		want *Field
461	}{
462		// Exact match wins.
463		{"Abc", field("Abc", int(0), 1)},
464		{"AbC", field("AbC", int(0), 2)},
465		{"ABc", field("ABc", int(0), 0, 0)},
466		// If there are multiple matches but no exact match or tag,
467		// the first field wins, lexicographically by index.
468		// Here, "ABc" is at a deeper embedding level, but since S4 appears
469		// first in S3, its index precedes the other fields of S3.
470		{"abc", field("ABc", int(0), 0, 0)},
471		// Tag name takes precedence over untagged field of the same name.
472		{"Tag", tfield("Tag", int(0), 4)},
473		// Unexported fields disappear.
474		{"unexported", nil},
475		// Untagged embedded structs disappear.
476		{"S4", nil},
477	} {
478		if got := fields.Match(test.name); !fieldsEqual(got, test.want) {
479			t.Errorf("match %q:\ngot  %+v\nwant %+v", test.name, got, test.want)
480		}
481	}
482}
483
484func TestAgainstJSONMatchingField(t *testing.T) {
485	s3 := S3{
486		S4:         S4{ABc: 1, Y: 2},
487		Abc:        3,
488		AbC:        4,
489		Tag:        5,
490		X:          6,
491		unexported: 7,
492	}
493	var want S3
494	jsonRoundTrip(t, s3, &want)
495	v := reflect.ValueOf(want)
496	fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
497	if err != nil {
498		t.Fatal(err)
499	}
500	for _, test := range []struct {
501		name string
502		got  int
503	}{
504		{"Abc", 3},
505		{"AbC", 4},
506		{"ABc", 1},
507		{"abc", 1},
508		{"Tag", 6},
509	} {
510		f := fields.Match(test.name)
511		if f == nil {
512			t.Fatalf("%s: no match", test.name)
513		}
514		w := v.FieldByIndex(f.Index).Interface()
515		if test.got != w {
516			t.Errorf("%s: got %d, want %d", test.name, test.got, w)
517		}
518	}
519}
520
521func TestTagErrors(t *testing.T) {
522	called := false
523	c := NewCache(func(t reflect.StructTag) (string, bool, interface{}, error) {
524		called = true
525		s := t.Get("f")
526		if s == "bad" {
527			return "", false, nil, errors.New("error")
528		}
529		return s, true, nil, nil
530	}, nil, nil)
531
532	type T struct {
533		X int `f:"ok"`
534		Y int `f:"bad"`
535	}
536
537	_, err := c.Fields(reflect.TypeOf(T{}))
538	if !called {
539		t.Fatal("tag parser not called")
540	}
541	if err == nil {
542		t.Error("want error, got nil")
543	}
544	// Second time, we should cache the error.
545	called = false
546	_, err = c.Fields(reflect.TypeOf(T{}))
547	if called {
548		t.Fatal("tag parser called on second time")
549	}
550	if err == nil {
551		t.Error("want error, got nil")
552	}
553}
554