k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/internal/third_party/go-json-experiment/json/fields_test.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package json 6 7 import ( 8 "encoding" 9 "errors" 10 "reflect" 11 "testing" 12 ) 13 14 type unexported struct{} 15 16 func TestMakeStructFields(t *testing.T) { 17 type Embed struct { 18 Foo string 19 } 20 type Recursive struct { 21 A string 22 *Recursive `json:",inline"` 23 B string 24 } 25 type MapStringAny map[string]any 26 tests := []struct { 27 name testName 28 in any 29 want structFields 30 wantErr error 31 }{{ 32 name: name("Names"), 33 in: struct { 34 F1 string 35 F2 string `json:"-"` 36 F3 string `json:"json_name"` 37 f3 string 38 F5 string `json:"json_name_nocase,nocase"` 39 }{}, 40 want: structFields{ 41 flattened: []structField{ 42 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "F1", quotedName: `"F1"`}}, 43 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "json_name", quotedName: `"json_name"`, hasName: true}}, 44 {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "json_name_nocase", quotedName: `"json_name_nocase"`, hasName: true, nocase: true}}, 45 }, 46 }, 47 }, { 48 name: name("BreadthFirstSearch"), 49 in: struct { 50 L1A string 51 L1B struct { 52 L2A string 53 L2B struct { 54 L3A string 55 } `json:",inline"` 56 L2C string 57 } `json:",inline"` 58 L1C string 59 L1D struct { 60 L2D string 61 L2E struct { 62 L3B string 63 } `json:",inline"` 64 L2F string 65 } `json:",inline"` 66 L1E string 67 }{}, 68 want: structFields{ 69 flattened: []structField{ 70 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "L1A", quotedName: `"L1A"`}}, 71 {id: 3, index: []int{1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2A", quotedName: `"L2A"`}}, 72 {id: 7, index: []int{1, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3A", quotedName: `"L3A"`}}, 73 {id: 4, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2C", quotedName: `"L2C"`}}, 74 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "L1C", quotedName: `"L1C"`}}, 75 {id: 5, index: []int{3, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2D", quotedName: `"L2D"`}}, 76 {id: 8, index: []int{3, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3B", quotedName: `"L3B"`}}, 77 {id: 6, index: []int{3, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2F", quotedName: `"L2F"`}}, 78 {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "L1E", quotedName: `"L1E"`}}, 79 }, 80 }, 81 }, { 82 name: name("NameResolution"), 83 in: struct { 84 X1 struct { 85 X struct { 86 A string // loses in precedence to A 87 B string // cancels out with X2.X.B 88 D string // loses in precedence to D 89 } `json:",inline"` 90 } `json:",inline"` 91 X2 struct { 92 X struct { 93 B string // cancels out with X1.X.B 94 C string 95 D string // loses in precedence to D 96 } `json:",inline"` 97 } `json:",inline"` 98 A string // takes precedence over X1.X.A 99 D string // takes precedence over X1.X.D and X2.X.D 100 }{}, 101 want: structFields{ 102 flattened: []structField{ 103 {id: 2, index: []int{1, 0, 1}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}}, 104 {id: 0, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}}, 105 {id: 1, index: []int{3}, typ: stringType, fieldOptions: fieldOptions{name: "D", quotedName: `"D"`}}, 106 }, 107 }, 108 }, { 109 name: name("Embed/Implicit"), 110 in: struct { 111 Embed 112 }{}, 113 want: structFields{ 114 flattened: []structField{ 115 {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}}, 116 }, 117 }, 118 }, { 119 name: name("Embed/Explicit"), 120 in: struct { 121 Embed `json:",inline"` 122 }{}, 123 want: structFields{ 124 flattened: []structField{ 125 {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}}, 126 }, 127 }, 128 }, { 129 name: name("Recursive"), 130 in: struct { 131 A string 132 Recursive `json:",inline"` 133 C string 134 }{}, 135 want: structFields{ 136 flattened: []structField{ 137 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}}, 138 {id: 2, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "B", quotedName: `"B"`}}, 139 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}}, 140 }, 141 }, 142 }, { 143 name: name("InlinedFallback/Cancelation"), 144 in: struct { 145 X1 struct { 146 X RawValue `json:",inline"` 147 } `json:",inline"` 148 X2 struct { 149 X map[string]any `json:",unknown"` 150 } `json:",inline"` 151 }{}, 152 want: structFields{}, 153 }, { 154 name: name("InlinedFallback/Precedence"), 155 in: struct { 156 X1 struct { 157 X RawValue `json:",inline"` 158 } `json:",inline"` 159 X2 struct { 160 X map[string]any `json:",unknown"` 161 } `json:",inline"` 162 X map[string]RawValue `json:",unknown"` 163 }{}, 164 want: structFields{ 165 inlinedFallback: &structField{id: 0, index: []int{2}, typ: reflect.TypeOf(map[string]RawValue(nil)), fieldOptions: fieldOptions{name: "X", quotedName: `"X"`, unknown: true}}, 166 }, 167 }, { 168 name: name("InvalidUTF8"), 169 in: struct { 170 Name string `json:"'\\xde\\xad\\xbe\\xef'"` 171 }{}, 172 wantErr: errors.New(`Go struct field Name has JSON object name "ޭ\xbe\xef" with invalid UTF-8`), 173 }, { 174 name: name("DuplicateName"), 175 in: struct { 176 A string `json:"same"` 177 B string `json:"same"` 178 }{}, 179 wantErr: errors.New(`Go struct fields A and B conflict over JSON object name "same"`), 180 }, { 181 name: name("BothInlineAndUnknown"), 182 in: struct { 183 A struct{} `json:",inline,unknown"` 184 }{}, 185 wantErr: errors.New("Go struct field A cannot have both `inline` and `unknown` specified"), 186 }, { 187 name: name("InlineWithOptions"), 188 in: struct { 189 A struct{} `json:",inline,omitempty"` 190 }{}, 191 wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"), 192 }, { 193 name: name("UnknownWithOptions"), 194 in: struct { 195 A map[string]any `json:",inline,omitempty"` 196 }{}, 197 wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"), 198 }, { 199 name: name("InlineTextMarshaler"), 200 in: struct { 201 A struct{ encoding.TextMarshaler } `json:",inline"` 202 }{}, 203 wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextMarshaler } must not implement JSON marshal or unmarshal methods`), 204 }, { 205 name: name("UnknownJSONMarshalerV1"), 206 in: struct { 207 A struct{ MarshalerV1 } `json:",unknown"` 208 }{}, 209 wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerV1 } must not implement JSON marshal or unmarshal methods`), 210 }, { 211 name: name("InlineJSONMarshalerV2"), 212 in: struct { 213 A struct{ MarshalerV2 } `json:",inline"` 214 }{}, 215 wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerV2 } must not implement JSON marshal or unmarshal methods`), 216 }, { 217 name: name("UnknownTextUnmarshaler"), 218 in: struct { 219 A *struct{ encoding.TextUnmarshaler } `json:",unknown"` 220 }{}, 221 wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextUnmarshaler } must not implement JSON marshal or unmarshal methods`), 222 }, { 223 name: name("InlineJSONUnmarshalerV1"), 224 in: struct { 225 A *struct{ UnmarshalerV1 } `json:",inline"` 226 }{}, 227 wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerV1 } must not implement JSON marshal or unmarshal methods`), 228 }, { 229 name: name("UnknownJSONUnmarshalerV2"), 230 in: struct { 231 A struct{ UnmarshalerV2 } `json:",unknown"` 232 }{}, 233 wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerV2 } must not implement JSON marshal or unmarshal methods`), 234 }, { 235 name: name("UnknownStruct"), 236 in: struct { 237 A struct { 238 X, Y, Z string 239 } `json:",unknown"` 240 }{}, 241 wantErr: errors.New("inlined Go struct field A of type struct { X string; Y string; Z string } with `unknown` tag must be a Go map of string key or a json.RawValue"), 242 }, { 243 name: name("InlineUnsupported/MapIntKey"), 244 in: struct { 245 A map[int]any `json:",unknown"` 246 }{}, 247 wantErr: errors.New(`inlined Go struct field A of type map[int]interface {} must be a Go struct, Go map of string key, or json.RawValue`), 248 }, { 249 name: name("InlineUnsupported/MapNamedStringKey"), 250 in: struct { 251 A map[namedString]any `json:",inline"` 252 }{}, 253 wantErr: errors.New(`inlined Go struct field A of type map[json.namedString]interface {} must be a Go struct, Go map of string key, or json.RawValue`), 254 }, { 255 name: name("InlineUnsupported/DoublePointer"), 256 in: struct { 257 A **struct{} `json:",inline"` 258 }{}, 259 wantErr: errors.New(`inlined Go struct field A of type *struct {} must be a Go struct, Go map of string key, or json.RawValue`), 260 }, { 261 name: name("DuplicateInline"), 262 in: struct { 263 A map[string]any `json:",inline"` 264 B RawValue `json:",inline"` 265 }{}, 266 wantErr: errors.New(`inlined Go struct fields A and B cannot both be a Go map or json.RawValue`), 267 }, { 268 name: name("DuplicateEmbedInline"), 269 in: struct { 270 MapStringAny 271 B RawValue `json:",inline"` 272 }{}, 273 wantErr: errors.New(`inlined Go struct fields MapStringAny and B cannot both be a Go map or json.RawValue`), 274 }} 275 276 for _, tt := range tests { 277 t.Run(tt.name.name, func(t *testing.T) { 278 got, err := makeStructFields(reflect.TypeOf(tt.in)) 279 280 // Sanity check that pointers are consistent. 281 pointers := make(map[*structField]bool) 282 for i := range got.flattened { 283 pointers[&got.flattened[i]] = true 284 } 285 for _, f := range got.byActualName { 286 if !pointers[f] { 287 t.Errorf("%s: byActualName pointer not in flattened", tt.name.where) 288 } 289 } 290 for _, fs := range got.byFoldedName { 291 for _, f := range fs { 292 if !pointers[f] { 293 t.Errorf("%s: byFoldedName pointer not in flattened", tt.name.where) 294 } 295 } 296 } 297 298 // Zero out fields that are incomparable. 299 for i := range got.flattened { 300 got.flattened[i].fncs = nil 301 got.flattened[i].isEmpty = nil 302 } 303 if got.inlinedFallback != nil { 304 got.inlinedFallback.fncs = nil 305 got.inlinedFallback.isEmpty = nil 306 } 307 308 // Reproduce maps in want. 309 if tt.wantErr == nil { 310 tt.want.byActualName = make(map[string]*structField) 311 for i := range tt.want.flattened { 312 f := &tt.want.flattened[i] 313 tt.want.byActualName[f.name] = f 314 } 315 tt.want.byFoldedName = make(map[string][]*structField) 316 for i, f := range tt.want.flattened { 317 foldedName := string(foldName([]byte(f.name))) 318 tt.want.byFoldedName[foldedName] = append(tt.want.byFoldedName[foldedName], &tt.want.flattened[i]) 319 } 320 } 321 322 // Only compare underlying error to simplify test logic. 323 var gotErr error 324 if err != nil { 325 gotErr = err.Err 326 } 327 328 if !reflect.DeepEqual(got, tt.want) || !reflect.DeepEqual(gotErr, tt.wantErr) { 329 t.Errorf("%s: makeStructFields(%T):\n\tgot (%v, %v)\n\twant (%v, %v)", tt.name.where, tt.in, got, gotErr, tt.want, tt.wantErr) 330 } 331 }) 332 } 333 } 334 335 func TestParseTagOptions(t *testing.T) { 336 tests := []struct { 337 name testName 338 in any // must be a struct with a single field 339 wantOpts fieldOptions 340 wantErr error 341 }{{ 342 name: name("GoName"), 343 in: struct { 344 FieldName int 345 }{}, 346 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, 347 }, { 348 name: name("GoNameWithOptions"), 349 in: struct { 350 FieldName int `json:",inline"` 351 }{}, 352 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true}, 353 }, { 354 name: name("Empty"), 355 in: struct { 356 V int `json:""` 357 }{}, 358 wantOpts: fieldOptions{name: "V", quotedName: `"V"`}, 359 }, { 360 name: name("Unexported"), 361 in: struct { 362 v int `json:"Hello"` 363 }{}, 364 wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"Hello\"` tag"), 365 }, { 366 name: name("UnexportedEmpty"), 367 in: struct { 368 v int `json:""` 369 }{}, 370 wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"\"` tag"), 371 }, { 372 name: name("EmbedUnexported"), 373 in: struct { 374 unexported 375 }{}, 376 wantErr: errors.New("embedded Go struct field unexported of an unexported type must be explicitly ignored with a `json:\"-\"` tag"), 377 }, { 378 name: name("Ignored"), 379 in: struct { 380 V int `json:"-"` 381 }{}, 382 wantErr: errIgnoredField, 383 }, { 384 name: name("IgnoredEmbedUnexported"), 385 in: struct { 386 unexported `json:"-"` 387 }{}, 388 wantErr: errIgnoredField, 389 }, { 390 name: name("DashComma"), 391 in: struct { 392 V int `json:"-,"` 393 }{}, 394 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"), 395 }, { 396 name: name("QuotedDashName"), 397 in: struct { 398 V int `json:"'-'"` 399 }{}, 400 wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`}, 401 }, { 402 name: name("LatinPunctuationName"), 403 in: struct { 404 V int `json:"$%-/"` 405 }{}, 406 wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`}, 407 }, { 408 name: name("QuotedLatinPunctuationName"), 409 in: struct { 410 V int `json:"'$%-/'"` 411 }{}, 412 wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`}, 413 }, { 414 name: name("LatinDigitsName"), 415 in: struct { 416 V int `json:"0123456789"` 417 }{}, 418 wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`}, 419 }, { 420 name: name("QuotedLatinDigitsName"), 421 in: struct { 422 V int `json:"'0123456789'"` 423 }{}, 424 wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`}, 425 }, { 426 name: name("LatinUppercaseName"), 427 in: struct { 428 V int `json:"ABCDEFGHIJKLMOPQRSTUVWXYZ"` 429 }{}, 430 wantOpts: fieldOptions{hasName: true, name: "ABCDEFGHIJKLMOPQRSTUVWXYZ", quotedName: `"ABCDEFGHIJKLMOPQRSTUVWXYZ"`}, 431 }, { 432 name: name("LatinLowercaseName"), 433 in: struct { 434 V int `json:"abcdefghijklmnopqrstuvwxyz_"` 435 }{}, 436 wantOpts: fieldOptions{hasName: true, name: "abcdefghijklmnopqrstuvwxyz_", quotedName: `"abcdefghijklmnopqrstuvwxyz_"`}, 437 }, { 438 name: name("GreekName"), 439 in: struct { 440 V string `json:"Ελλάδα"` 441 }{}, 442 wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`}, 443 }, { 444 name: name("QuotedGreekName"), 445 in: struct { 446 V string `json:"'Ελλάδα'"` 447 }{}, 448 wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`}, 449 }, { 450 name: name("ChineseName"), 451 in: struct { 452 V string `json:"世界"` 453 }{}, 454 wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`}, 455 }, { 456 name: name("QuotedChineseName"), 457 in: struct { 458 V string `json:"'世界'"` 459 }{}, 460 wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`}, 461 }, { 462 name: name("PercentSlashName"), 463 in: struct { 464 V int `json:"text/html%"` 465 }{}, 466 wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`}, 467 }, { 468 name: name("QuotedPercentSlashName"), 469 in: struct { 470 V int `json:"'text/html%'"` 471 }{}, 472 wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`}, 473 }, { 474 name: name("PunctuationName"), 475 in: struct { 476 V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` 477 }{}, 478 wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`}, 479 }, { 480 name: name("QuotedPunctuationName"), 481 in: struct { 482 V string `json:"'!#$%&()*+-./:;<=>?@[]^_{|}~ '"` 483 }{}, 484 wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`}, 485 }, { 486 name: name("EmptyName"), 487 in: struct { 488 V int `json:"''"` 489 }{}, 490 wantOpts: fieldOptions{hasName: true, name: "", quotedName: `""`}, 491 }, { 492 name: name("SpaceName"), 493 in: struct { 494 V int `json:"' '"` 495 }{}, 496 wantOpts: fieldOptions{hasName: true, name: " ", quotedName: `" "`}, 497 }, { 498 name: name("CommaQuotes"), 499 in: struct { 500 V int `json:"',\\'\"\\\"'"` 501 }{}, 502 wantOpts: fieldOptions{hasName: true, name: `,'""`, quotedName: `",'\"\""`}, 503 }, { 504 name: name("SingleComma"), 505 in: struct { 506 V int `json:","` 507 }{}, 508 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"), 509 }, { 510 name: name("SuperfluousCommas"), 511 in: struct { 512 V int `json:",,,,\"\",,inline,unknown,,,,"` 513 }{}, 514 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid character ',' at start of option (expecting Unicode letter or single quote)"), 515 }, { 516 name: name("NoCaseOption"), 517 in: struct { 518 FieldName int `json:",nocase"` 519 }{}, 520 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, nocase: true}, 521 }, { 522 name: name("InlineOption"), 523 in: struct { 524 FieldName int `json:",inline"` 525 }{}, 526 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true}, 527 }, { 528 name: name("UnknownOption"), 529 in: struct { 530 FieldName int `json:",unknown"` 531 }{}, 532 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, unknown: true}, 533 }, { 534 name: name("OmitZeroOption"), 535 in: struct { 536 FieldName int `json:",omitzero"` 537 }{}, 538 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitzero: true}, 539 }, { 540 name: name("OmitEmptyOption"), 541 in: struct { 542 FieldName int `json:",omitempty"` 543 }{}, 544 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitempty: true}, 545 }, { 546 name: name("StringOption"), 547 in: struct { 548 FieldName int `json:",string"` 549 }{}, 550 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, string: true}, 551 }, { 552 name: name("FormatOptionEqual"), 553 in: struct { 554 FieldName int `json:",format=fizzbuzz"` 555 }{}, 556 wantErr: errors.New("Go struct field FieldName is missing value for `format` tag option"), 557 }, { 558 name: name("FormatOptionColon"), 559 in: struct { 560 FieldName int `json:",format:fizzbuzz"` 561 }{}, 562 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "fizzbuzz"}, 563 }, { 564 name: name("FormatOptionQuoted"), 565 in: struct { 566 FieldName int `json:",format:'2006-01-02'"` 567 }{}, 568 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "2006-01-02"}, 569 }, { 570 name: name("FormatOptionInvalid"), 571 in: struct { 572 FieldName int `json:",format:'2006-01-02"` 573 }{}, 574 wantErr: errors.New("Go struct field FieldName has malformed value for `format` tag option: single-quoted string not terminated: '2006-01-0..."), 575 }, { 576 name: name("FormatOptionNotLast"), 577 in: struct { 578 FieldName int `json:",format:alpha,ordered"` 579 }{}, 580 wantErr: errors.New("Go struct field FieldName has `format` tag option that was not specified last"), 581 }, { 582 name: name("AllOptions"), 583 in: struct { 584 FieldName int `json:",nocase,inline,unknown,omitzero,omitempty,string,format:format"` 585 }{}, 586 wantOpts: fieldOptions{ 587 name: "FieldName", 588 quotedName: `"FieldName"`, 589 nocase: true, 590 inline: true, 591 unknown: true, 592 omitzero: true, 593 omitempty: true, 594 string: true, 595 format: "format", 596 }, 597 }, { 598 name: name("AllOptionsQuoted"), 599 in: struct { 600 FieldName int `json:",'nocase','inline','unknown','omitzero','omitempty','string','format':'format'"` 601 }{}, 602 wantErr: errors.New("Go struct field FieldName has unnecessarily quoted appearance of `'nocase'` tag option; specify `nocase` instead"), 603 }, { 604 name: name("AllOptionsCaseSensitive"), 605 in: struct { 606 FieldName int `json:",NOCASE,INLINE,UNKNOWN,OMITZERO,OMITEMPTY,STRING,FORMAT:FORMAT"` 607 }{}, 608 wantErr: errors.New("Go struct field FieldName has invalid appearance of `NOCASE` tag option; specify `nocase` instead"), 609 }, { 610 name: name("AllOptionsSpaceSensitive"), 611 in: struct { 612 FieldName int `json:", nocase , inline , unknown , omitzero , omitempty , string , format:format "` 613 }{}, 614 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character ' ' at start of option (expecting Unicode letter or single quote)"), 615 }, { 616 name: name("UnknownTagOption"), 617 in: struct { 618 FieldName int `json:",inline,whoknows,string"` 619 }{}, 620 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true, string: true}, 621 }, { 622 name: name("MalformedQuotedString/MissingQuote"), 623 in: struct { 624 FieldName int `json:"'hello,string"` 625 }{}, 626 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: single-quoted string not terminated: 'hello,str..."), 627 }, { 628 name: name("MalformedQuotedString/MissingComma"), 629 in: struct { 630 FieldName int `json:"'hello'inline,string"` 631 }{}, 632 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character 'i' before next option (expecting ',')"), 633 }, { 634 name: name("MalformedQuotedString/InvalidEscape"), 635 in: struct { 636 FieldName int `json:"'hello\\u####',inline,string"` 637 }{}, 638 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid single-quoted string: 'hello\\u####'"), 639 }, { 640 name: name("MisnamedTag"), 641 in: struct { 642 V int `jsom:"Misnamed"` 643 }{}, 644 wantOpts: fieldOptions{name: "V", quotedName: `"V"`}, 645 }} 646 647 for _, tt := range tests { 648 t.Run(tt.name.name, func(t *testing.T) { 649 fs := reflect.TypeOf(tt.in).Field(0) 650 gotOpts, gotErr := parseFieldOptions(fs) 651 if !reflect.DeepEqual(gotOpts, tt.wantOpts) || !reflect.DeepEqual(gotErr, tt.wantErr) { 652 t.Errorf("%s: parseFieldOptions(%T) = (%v, %v), want (%v, %v)", tt.name.where, tt.in, gotOpts, gotErr, tt.wantOpts, tt.wantErr) 653 } 654 }) 655 } 656 }