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