knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/field_error_test.go (about) 1 /* 2 Copyright 2017 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package apis 18 19 import ( 20 "strconv" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 ) 28 29 type testStruct struct { 30 Name string `json:"name"` 31 } 32 33 type unexported struct { 34 unexportedField int 35 } 36 37 func TestFieldError(t *testing.T) { 38 tests := []struct { 39 name string 40 err *FieldError 41 prefixes [][]string 42 want string 43 }{{ 44 name: "simple single no propagation", 45 err: &FieldError{ 46 Message: "hear me roar", 47 Paths: []string{"foo.bar"}, 48 }, 49 want: "hear me roar: foo.bar", 50 }, { 51 name: "simple single propagation", 52 err: &FieldError{ 53 Message: `invalid value "blah"`, 54 Paths: []string{"foo"}, 55 }, 56 prefixes: [][]string{{"bar"}, {"baz", "ugh"}, {"hoola"}}, 57 want: `invalid value "blah": hoola.baz.ugh.bar.foo`, 58 }, { 59 name: "simple multiple propagation", 60 err: &FieldError{ 61 Message: "invalid field(s)", 62 Paths: []string{"foo", "bar"}, 63 }, 64 prefixes: [][]string{{"baz", "ugh"}}, 65 want: "invalid field(s): baz.ugh.bar, baz.ugh.foo", 66 }, { 67 name: "multiple propagation with details", 68 err: &FieldError{ 69 Message: "invalid field(s)", 70 Paths: []string{"foo", "bar"}, 71 Details: `I am a long 72 long 73 loooong 74 Body.`, 75 }, 76 prefixes: [][]string{{"baz", "ugh"}}, 77 want: `invalid field(s): baz.ugh.bar, baz.ugh.foo 78 I am a long 79 long 80 loooong 81 Body.`, 82 }, { 83 name: "single propagation, empty start", 84 err: &FieldError{ 85 Message: "invalid field(s)", 86 // We might see this validating a scalar leaf. 87 Paths: []string{CurrentField}, 88 }, 89 prefixes: [][]string{{"baz", "ugh"}}, 90 want: "invalid field(s): baz.ugh", 91 }, { 92 name: "single propagation, no paths", 93 err: &FieldError{ 94 Message: "invalid field(s)", 95 Paths: nil, 96 }, 97 prefixes: [][]string{{"baz", "ugh"}}, 98 want: "invalid field(s): ", 99 }, { 100 name: "nil propagation", 101 err: nil, 102 prefixes: [][]string{{"baz", "ugh"}}, 103 }, { 104 name: "missing field propagation", 105 err: ErrMissingField("foo", "bar"), 106 prefixes: [][]string{{"baz"}}, 107 want: "missing field(s): baz.bar, baz.foo", 108 }, { 109 name: "check disallowed - none found", 110 err: CheckDisallowedFields( 111 testStruct{ 112 Name: "foo", 113 }, 114 testStruct{ 115 Name: "foo", 116 }), 117 prefixes: [][]string{{"baz"}}, 118 want: "", 119 }, { 120 name: "check disallowed internal error", 121 err: CheckDisallowedFields( 122 unexported{ 123 unexportedField: 2, 124 }, 125 unexported{ 126 unexportedField: 0, 127 }), 128 prefixes: [][]string{{"baz"}}, 129 want: "Internal Error: baz", 130 }, { 131 name: "check disallowed propagation", 132 err: CheckDisallowedFields( 133 testStruct{ 134 Name: "foo", 135 }, 136 testStruct{ 137 Name: "", 138 }), 139 prefixes: [][]string{{"baz"}}, 140 want: "must not set the field(s): baz.name", 141 }, { 142 name: "missing disallowed propagation", 143 err: ErrDisallowedFields("foo", "bar"), 144 prefixes: [][]string{{"baz"}}, 145 want: "must not set the field(s): baz.bar, baz.foo", 146 }, { 147 name: "invalid value propagation", 148 err: ErrInvalidValue("foo", "bar"), 149 prefixes: [][]string{{"baz"}}, 150 want: `invalid value: foo: baz.bar`, 151 }, { 152 name: "invalid value propagation (int)", 153 err: ErrInvalidValue(5, "bar"), 154 prefixes: [][]string{{"baz"}}, 155 want: `invalid value: 5: baz.bar`, 156 }, { 157 name: "invalid value propagation (duration)", 158 err: ErrInvalidValue(5*time.Second, "bar"), 159 prefixes: [][]string{{"baz"}}, 160 want: `invalid value: 5s: baz.bar`, 161 }, { 162 name: "invalid value details propagation", 163 err: ErrInvalidValue("foo", "bar", "qux"), 164 prefixes: [][]string{{"baz"}}, 165 want: "invalid value: foo: baz.bar\nqux", 166 }, { 167 name: "invalid value propagation", 168 err: ErrGeneric("this is a generic error", "foo", "bar"), 169 prefixes: [][]string{{"baz"}}, 170 want: `this is a generic error: baz.bar, baz.foo`, 171 }, { 172 name: "missing mutually exclusive fields", 173 err: ErrMissingOneOf("foo", "bar"), 174 prefixes: [][]string{{"baz"}}, 175 want: `expected exactly one, got neither: baz.bar, baz.foo`, 176 }, { 177 name: "multiple mutually exclusive fields", 178 err: ErrMultipleOneOf("foo", "bar"), 179 prefixes: [][]string{{"baz"}}, 180 want: `expected exactly one, got both: baz.bar, baz.foo`, 181 }, { 182 name: "invalid key name", 183 err: ErrInvalidKeyName("b@r", "foo[0].name", 184 "can not use @", "do not try"), 185 prefixes: [][]string{{"baz"}}, 186 want: `invalid key name "b@r": baz.foo[0].name 187 can not use @, do not try`, 188 }, { 189 name: "invalid key name with details array", 190 err: ErrInvalidKeyName("b@r", "foo[0].name", 191 []string{"can not use @", "do not try"}...), 192 prefixes: [][]string{{"baz"}}, 193 want: `invalid key name "b@r": baz.foo[0].name 194 can not use @, do not try`, 195 }, { 196 name: "disallowed update deprecated fields", 197 err: ErrDisallowedUpdateDeprecatedFields("foo", "bar"), 198 prefixes: [][]string{{"baz"}}, 199 want: `must not update deprecated field(s): baz.bar, baz.foo`, 200 }, { 201 name: "very complex to simple", 202 err: func() *FieldError { 203 fe := &FieldError{ 204 Message: "First", 205 Paths: []string{"A", "B", "C"}, 206 } 207 208 fe = fe.Also(fe).Also(fe).Also(fe).Also(fe) 209 210 fe = fe.Also(&FieldError{ 211 Message: "Second", 212 Paths: []string{"Z", "X", "Y"}, 213 }) 214 215 fe = fe.Also(fe).Also(fe).Also(fe).Also(fe) 216 217 return fe 218 }(), 219 want: `First: A, B, C 220 Second: X, Y, Z`, 221 }, { 222 name: "exponentially grows", 223 err: func() *FieldError { 224 fe := &FieldError{ 225 Message: "Top", 226 Paths: []string{"A", "B", "C"}, 227 } 228 229 for _, p := range []string{"3", "2", "1"} { 230 for range 3 { 231 fe = fe.Also(fe) 232 } 233 fe = fe.ViaField(p) 234 } 235 236 return fe 237 }(), 238 want: `Top: 1.2.3.A, 1.2.3.B, 1.2.3.C`, 239 }, { 240 name: "path grows but details are different", 241 err: func() *FieldError { 242 fe := &FieldError{ 243 Message: "Top", 244 Paths: []string{"A", "B", "C"}, 245 } 246 247 for _, p := range []string{"3", "2", "1"} { 248 e := fe.ViaField(p) 249 e.Details = "here at " + p 250 for range 3 { 251 fe = fe.Also(e) 252 } 253 } 254 255 return fe 256 }(), 257 want: `Top: A, B, C 258 Top: 1.A, 1.B, 1.C 259 here at 1 260 Top: 1.2.A, 1.2.B, 1.2.C, 2.A, 2.B, 2.C 261 here at 2 262 Top: 1.2.3.A, 1.2.3.B, 1.2.3.C, 1.3.A, 1.3.B, 1.3.C, 2.3.A, 2.3.B, 2.3.C, 3.A, 3.B, 3.C 263 here at 3`, 264 }, { 265 name: "very complex to complex", 266 err: func() *FieldError { 267 fe := &FieldError{ 268 Message: "First", 269 Paths: []string{"A", "B", "C"}, 270 } 271 272 fe = fe.ViaField("one").Also(fe).ViaField("two").Also(fe).ViaField("three").Also(fe) 273 274 fe = fe.Also(&FieldError{ 275 Message: "Second", 276 Paths: []string{"Z", "X", "Y"}, 277 }) 278 279 return fe 280 }(), 281 want: `First: A, B, C, three.A, three.B, three.C, three.two.A, three.two.B, three.two.C, three.two.one.A, three.two.one.B, three.two.one.C 282 Second: X, Y, Z`, 283 }, { 284 name: "out of bound value", 285 err: ErrOutOfBoundsValue("a", "b", "c", "string"), 286 prefixes: [][]string{{"spec"}}, 287 want: `expected b <= a <= c: spec.string`, 288 }, { 289 name: "out of bound value (int)", 290 err: ErrOutOfBoundsValue(-1, 0, 5, "timeout"), 291 prefixes: [][]string{{"spec"}}, 292 want: `expected 0 <= -1 <= 5: spec.timeout`, 293 }, { 294 name: "out of bound value (time.Duration)", 295 err: ErrOutOfBoundsValue(1*time.Second, 2*time.Second, 5*time.Second, "timeout"), 296 prefixes: [][]string{{"spec"}}, 297 want: `expected 2s <= 1s <= 5s: spec.timeout`, 298 }, { 299 name: "Explicit Error", 300 err: ErrMissingField("foo", "bar").At(ErrorLevel), 301 want: `missing field(s): bar, foo`, 302 }, { 303 name: "Warning", 304 err: ErrMissingField("foo", "bar").At(WarningLevel), 305 want: `missing field(s): bar, foo`, 306 }, { 307 name: "Explicit Error isn't filtered", 308 err: ErrMissingField("foo", "bar").At(ErrorLevel).Filter(ErrorLevel), 309 want: `missing field(s): bar, foo`, 310 }, { 311 name: "Implicit Error isn't filtered", 312 err: ErrMissingField("foo", "bar").Filter(ErrorLevel), 313 want: `missing field(s): bar, foo`, 314 }, { 315 name: "Warning isn't filtered", 316 err: ErrMissingField("foo", "bar").At(WarningLevel).Filter(WarningLevel), 317 want: `missing field(s): bar, foo`, 318 }, { 319 name: "Explicit Error is filtered", 320 err: ErrMissingField("foo", "bar").At(ErrorLevel).Filter(WarningLevel), 321 }, { 322 name: "Warning is filtered", 323 err: ErrMissingField("foo", "bar").At(WarningLevel).Filter(ErrorLevel), 324 }, { 325 name: "Mix of Errors and Warnings", 326 err: ErrMissingField("foo").Also(ErrMissingField("bar").At(WarningLevel)), 327 want: `missing field(s): foo 328 missing field(s): bar`, 329 }, { 330 name: "Mix of Errors and Warnings - Filter Warning", 331 err: ErrMissingField("foo").Also(ErrMissingField("bar").At(WarningLevel)).Filter(WarningLevel), 332 want: `missing field(s): bar`, 333 }, { 334 name: "Mix of Errors and Warnings - Filter Error", 335 err: ErrMissingField("foo").Also(ErrMissingField("bar").At(WarningLevel)).Filter(ErrorLevel), 336 want: `missing field(s): foo`, 337 }, { 338 name: "Mix of Errors and Warnings turned to Warnings", 339 err: ErrMissingField("foo").Also(ErrMissingField("bar").At(WarningLevel)).At(WarningLevel).Filter(WarningLevel), 340 want: `missing field(s): bar, foo`, 341 }, { 342 name: "Mix of Errors and Warnings turned to Errors", 343 err: ErrMissingField("foo").Also(ErrMissingField("bar").At(WarningLevel)).At(ErrorLevel).Filter(ErrorLevel), 344 want: `missing field(s): bar, foo`, 345 }} 346 347 for _, test := range tests { 348 t.Run(test.name, func(t *testing.T) { 349 fe := test.err 350 // Simulate propagation up a call stack. 351 for _, prefix := range test.prefixes { 352 fe = fe.ViaField(prefix...) 353 } 354 if test.want != "" { 355 if got, want := fe.Error(), test.want; got != want { 356 t.Errorf("%s: Error() = %v, wanted %v", test.name, got, want) 357 } 358 } else if fe != nil { 359 t.Errorf("%s: ViaField() = %v, wanted nil", test.name, fe) 360 } 361 }) 362 } 363 } 364 365 func TestWrappedErrors(t *testing.T) { 366 for _, test := range []struct { 367 name string 368 err *FieldError 369 want []*FieldError 370 }{{ 371 name: "nil", 372 err: nil, 373 want: nil, 374 }, { 375 name: "empty", 376 err: &FieldError{}, 377 want: nil, 378 }, { 379 name: "has errors", 380 err: (&FieldError{}).Also( 381 ErrInvalidValue("42", "answer"), 382 ErrGeneric("generic", "error"), 383 ), 384 want: []*FieldError{ 385 ErrGeneric("generic", "error"), 386 ErrInvalidValue("42", "answer"), 387 }, 388 }} { 389 t.Run(test.name, func(t *testing.T) { 390 if diff := cmp.Diff(test.want, test.err.WrappedErrors(), cmp.AllowUnexported(FieldError{}), cmpopts.EquateEmpty()); diff != "" { 391 t.Errorf("unexpected GetErrors() -want,+got: %s", diff) 392 } 393 }) 394 } 395 } 396 397 func TestViaIndexOrKeyFieldError(t *testing.T) { 398 tests := []struct { 399 name string 400 err *FieldError 401 prefixes [][]string 402 want string 403 }{{ 404 name: "nil", 405 err: nil, 406 want: "", 407 }, { 408 name: "nil with prefix", 409 err: nil, 410 prefixes: [][]string{{"INDEX:2"}, {"KEY:B"}, {"FIELDINDEX:6,AAA"}, {"FIELDKEY:bee,AAA"}}, 411 want: "", 412 }, { 413 name: "simple single no propagation", 414 err: &FieldError{ 415 Message: "hear me roar", 416 Paths: []string{"bar"}, 417 }, 418 prefixes: [][]string{{"INDEX:3", "INDEX:2", "INDEX:1", "foo"}}, 419 want: "hear me roar: foo[1][2][3].bar", 420 }, { 421 name: "simple key", 422 err: &FieldError{ 423 Message: "hear me roar", 424 Paths: []string{"bar"}, 425 }, 426 prefixes: [][]string{{"KEY:C", "KEY:B", "KEY:A", "foo"}}, 427 want: "hear me roar: foo[A][B][C].bar", 428 }, { 429 name: "missing field propagation", 430 err: ErrMissingField("foo", "bar"), 431 prefixes: [][]string{{"[2]", "baz"}}, 432 want: "missing field(s): baz[2].bar, baz[2].foo", 433 }, { 434 name: "invalid key name", 435 err: ErrInvalidKeyName("b@r", "name", 436 "can not use @", "do not try"), 437 prefixes: [][]string{{"baz", "INDEX:0", "foo"}}, 438 want: `invalid key name "b@r": foo[0].baz.name 439 can not use @, do not try`, 440 }, { 441 name: "invalid key name with keys", 442 err: ErrInvalidKeyName("b@r", "name", 443 "can not use @", "do not try"), 444 prefixes: [][]string{{"baz", "INDEX:0", "foo"}, {"bar", "KEY:A", "boo"}}, 445 want: `invalid key name "b@r": boo[A].bar.foo[0].baz.name 446 can not use @, do not try`, 447 }, { 448 name: "multi prefixes provided", 449 err: &FieldError{ 450 Message: "invalid field(s)", 451 Paths: []string{"foo"}, 452 }, 453 prefixes: [][]string{{"INDEX:2"}, {"bee"}, {"INDEX:0"}, {"baa", "baz", "ugh"}}, 454 want: "invalid field(s): ugh.baz.baa[0].bee[2].foo", 455 }, { 456 name: "use helper viaFieldIndex", 457 err: &FieldError{ 458 Message: "invalid field(s)", 459 Paths: []string{"foo"}, 460 }, 461 prefixes: [][]string{{"FIELDINDEX:bee,2"}, {"FIELDINDEX:baa,0"}, {"baz", "ugh"}}, 462 want: "invalid field(s): ugh.baz.baa[0].bee[2].foo", 463 }, { 464 name: "use helper viaFieldKey", 465 err: &FieldError{ 466 Message: "invalid field(s)", 467 Paths: []string{"foo"}, 468 }, 469 prefixes: [][]string{{"FIELDKEY:bee,AAA"}, {"FIELDKEY:baa,BBB"}, {"baz", "ugh"}}, 470 want: "invalid field(s): ugh.baz.baa[BBB].bee[AAA].foo", 471 }, { 472 name: "bypass helpers", 473 err: &FieldError{ 474 Message: "invalid field(s)", 475 Paths: []string{"foo"}, 476 }, 477 prefixes: [][]string{{"[2]"}, {"[1]"}, {"bar"}}, 478 want: "invalid field(s): bar[1][2].foo", 479 }, { 480 name: "multi paths provided", 481 err: &FieldError{ 482 Message: "invalid field(s)", 483 Paths: []string{"foo", "bar"}, 484 }, 485 prefixes: [][]string{{"INDEX:0"}, {"index"}, {"KEY:A"}, {"map"}}, 486 want: "invalid field(s): map[A].index[0].bar, map[A].index[0].foo", 487 }, { 488 name: "manual index", 489 err: func() *FieldError { 490 // Example, return an error in a loop: 491 // for i, item := spec.myList { 492 // err := item.validate().ViaIndex(i).ViaField("myList") 493 // if err != nil { 494 // return err 495 // } 496 // } 497 // --> I expect path to be myList[i].foo 498 499 err := &FieldError{ 500 Message: "invalid field(s)", 501 Paths: []string{"foo"}, 502 } 503 504 err = err.ViaIndex(0).ViaField("bar") 505 err = err.ViaIndex(2).ViaIndex(1).ViaField("baz") 506 err = err.ViaIndex(3).ViaIndex(4).ViaField("boof") 507 return err 508 }(), 509 want: "invalid field(s): boof[4][3].baz[1][2].bar[0].foo", 510 }, { 511 name: "manual multiple index", 512 err: func() *FieldError { 513 err := &FieldError{ 514 Message: "invalid field(s)", 515 Paths: []string{"foo"}, 516 } 517 518 err = err.ViaField("bear", "[1]", "[2]", "[3]", "baz", "]xxx[").ViaField("bar") 519 return err 520 }(), 521 want: "invalid field(s): bar.bear[1][2][3].baz.]xxx[.foo", 522 }, { 523 name: "manual keys", 524 err: func() *FieldError { 525 err := &FieldError{ 526 Message: "invalid field(s)", 527 Paths: []string{"foo"}, 528 } 529 530 err = err.ViaKey("A").ViaField("bar") 531 err = err.ViaKey("CCC").ViaKey("BB").ViaField("baz") 532 err = err.ViaKey("E").ViaKey("F").ViaField("jar") 533 return err 534 }(), 535 want: "invalid field(s): jar[F][E].baz[BB][CCC].bar[A].foo", 536 }, { 537 name: "manual index and keys", 538 err: func() *FieldError { 539 err := &FieldError{ 540 Message: "invalid field(s)", 541 Paths: []string{"foo", "faa"}, 542 } 543 err = err.ViaKey("A").ViaField("bar") 544 err = err.ViaIndex(1).ViaField("baz") 545 err = err.ViaKey("E").ViaIndex(0).ViaField("jar") 546 return err 547 }(), 548 want: "invalid field(s): jar[0][E].baz[1].bar[A].faa, jar[0][E].baz[1].bar[A].foo", 549 }, { 550 name: "leaf field error with index", 551 err: func() *FieldError { 552 return ErrInvalidArrayValue("kapot", "indexed", 5) 553 }(), 554 want: `invalid value: kapot: indexed[5]`, 555 }, { 556 name: "leaf field error with index (int)", 557 err: func() *FieldError { 558 return ErrInvalidArrayValue(42, "indexed", 5) 559 }(), 560 want: `invalid value: 42: indexed[5]`, 561 }, { 562 name: "nil propagation", 563 err: nil, 564 prefixes: [][]string{{"baz", "ugh", "INDEX:0", "KEY:A"}}, 565 }} 566 567 for _, test := range tests { 568 t.Run(test.name, func(t *testing.T) { 569 fe := test.err 570 // Simulate propagation up a call stack. 571 for _, prefix := range test.prefixes { 572 for _, p := range prefix { 573 if strings.HasPrefix(p, "INDEX") { 574 index := strings.Split(p, ":") 575 fe = fe.ViaIndex(makeIndex(index[1])) 576 } else if strings.HasPrefix(p, "FIELDINDEX") { 577 index := strings.Split(p, ":") 578 fe = fe.ViaFieldIndex(makeFieldIndex(index[1])) 579 } else if strings.HasPrefix(p, "KEY") { 580 key := strings.Split(p, ":") 581 fe = fe.ViaKey(makeKey(key[1])) 582 } else if strings.HasPrefix(p, "FIELDKEY") { 583 index := strings.Split(p, ":") 584 fe = fe.ViaFieldKey(makeFieldKey(index[1])) 585 } else { 586 fe = fe.ViaField(p) 587 } 588 } 589 } 590 591 if test.want != "" { 592 if got, want := fe.Error(), test.want; got != want { 593 t.Errorf("%s: Error() = %q, wanted %q, diff: %s", test.name, got, want, cmp.Diff(got, want)) 594 } 595 } else if fe != nil { 596 t.Errorf("%s: ViaField() = %v, wanted nil", test.name, fe) 597 } 598 }) 599 } 600 } 601 602 func TestNilError(t *testing.T) { 603 var err *FieldError 604 if got, want := err.Error(), ""; got != want { 605 t.Errorf("got %v, wanted %v", got, want) 606 } 607 } 608 609 func TestAlso(t *testing.T) { 610 tests := []struct { 611 name string 612 err *FieldError 613 also []FieldError 614 prefixes [][]string 615 want string 616 }{{ 617 name: "nil", 618 err: nil, 619 also: []FieldError{{ 620 Message: "also this", 621 Paths: []string{"woo"}, 622 }}, 623 prefixes: [][]string{{"foo"}}, 624 want: "also this: foo.woo", 625 }, { 626 name: "nil all the way", 627 err: nil, 628 also: []FieldError{{}}, 629 want: "", 630 }, { 631 name: "simple", 632 err: &FieldError{ 633 Message: "hear me roar", 634 Paths: []string{"bar"}, 635 }, 636 also: []FieldError{{ 637 Message: "also this", 638 Paths: []string{"woo"}, 639 }}, 640 prefixes: [][]string{{"foo", "[A]", "[B]", "[C]"}}, 641 want: `also this: foo[A][B][C].woo 642 hear me roar: foo[A][B][C].bar`, 643 }, { 644 name: "lots of also", 645 err: &FieldError{ 646 Message: "knock knock", 647 Paths: []string{"foo"}, 648 }, 649 also: []FieldError{{ 650 Message: "also this", 651 Paths: []string{"A"}, 652 }, { 653 Message: "and this", 654 Paths: []string{"B"}, 655 }, { 656 Message: "not without this", 657 Paths: []string{"C"}, 658 }}, 659 prefixes: [][]string{{"bar"}}, 660 want: `also this: bar.A 661 and this: bar.B 662 knock knock: bar.foo 663 not without this: bar.C`, 664 }} 665 666 for _, test := range tests { 667 t.Run(test.name, func(t *testing.T) { 668 fe := test.err 669 670 for _, err := range test.also { 671 fe = fe.Also(&err) 672 } 673 674 // Simulate propagation up a call stack. 675 for _, prefix := range test.prefixes { 676 fe = fe.ViaField(prefix...) 677 } 678 679 if test.want != "" { 680 if got, want := fe.Error(), test.want; got != want { 681 t.Errorf("\nGot: %v,\nwant: %v diff(-want,+got)\n%s", got, want, cmp.Diff(want, got)) 682 } 683 } else if fe != nil { 684 t.Errorf("ViaField() = %v, wanted nil", fe) 685 } 686 }) 687 } 688 } 689 690 func TestAlsoNil(t *testing.T) { 691 errs := &FieldError{ 692 Message: "original", 693 Paths: []string{"foo"}, 694 } 695 errs = errs.Also(nil) 696 697 if got, want := errs.Error(), "original: foo"; got != want { 698 t.Errorf("TestAlsoNil: Also(nil).Error() = %v, wanted %v", got, want) 699 } 700 701 errs = errs.Also([]*FieldError{nil}...) 702 if got, want := errs.Error(), "original: foo"; got != want { 703 t.Errorf("TestAlsoNil: Also(nil).Error() = %v, wanted %v", got, want) 704 } 705 } 706 707 func TestMergeFieldErrors(t *testing.T) { 708 tests := []struct { 709 name string 710 err *FieldError 711 also []FieldError 712 prefixes [][]string 713 want string 714 }{{ 715 name: "simple", 716 err: &FieldError{ 717 Message: "A simple error message", 718 Paths: []string{"bar"}, 719 }, 720 also: []FieldError{{ 721 Message: "A simple error message", 722 Paths: []string{"foo"}, 723 }}, 724 want: `A simple error message: bar, foo`, 725 }, { 726 name: "conflict", 727 err: &FieldError{ 728 Message: "A simple error message", 729 Paths: []string{"bar", "foo"}, 730 }, 731 also: []FieldError{{ 732 Message: "A simple error message", 733 Paths: []string{"foo"}, 734 }}, 735 want: `A simple error message: bar, foo`, 736 }, { 737 name: "lots of also", 738 err: (&FieldError{ 739 Message: "this error", 740 Paths: []string{"bar", "foo"}, 741 }).Also(&FieldError{ 742 Message: "another", 743 Paths: []string{"right", "left"}, 744 }).ViaField("head"), 745 also: []FieldError{{ 746 Message: "An alpha error message", 747 Paths: []string{"A"}, 748 }, { 749 Message: "An alpha error message", 750 Paths: []string{"B"}, 751 }, { 752 Message: "An alpha error message", 753 Paths: []string{"B"}, 754 }, { 755 Message: "An alpha error message", 756 Paths: []string{"B"}, 757 }, { 758 Message: "An alpha error message", 759 Paths: []string{"B"}, 760 }, { 761 Message: "An alpha error message", 762 Paths: []string{"B"}, 763 }, { 764 Message: "An alpha error message", 765 Paths: []string{"B"}, 766 }, { 767 Message: "An alpha error message", 768 Paths: []string{"C"}, 769 }, { 770 Message: "An alpha error message", 771 Paths: []string{"D"}, 772 }, { 773 Message: "this error", 774 Paths: []string{"foo"}, 775 Details: "devil is in the details", 776 }, { 777 Message: "this error", 778 Paths: []string{"foo"}, 779 Details: "more details", 780 }}, 781 prefixes: [][]string{{"this"}}, 782 want: `An alpha error message: this.A, this.B, this.C, this.D 783 another: this.head.left, this.head.right 784 this error: this.head.bar, this.head.foo 785 this error: this.foo 786 devil is in the details 787 this error: this.foo 788 more details`, 789 }} 790 for _, test := range tests { 791 t.Run(test.name, func(t *testing.T) { 792 fe := test.err 793 for _, err := range test.also { 794 fe = fe.Also(&err) 795 } 796 // Simulate propagation up a call stack. 797 for _, prefix := range test.prefixes { 798 fe = fe.ViaField(prefix...) 799 } 800 if test.want != "" { 801 got := fe.Error() 802 if got != test.want { 803 t.Errorf("%s: Error() = %v, wanted %v", test.name, got, test.want) 804 } 805 } else if fe != nil { 806 t.Errorf("%s: ViaField() = %v, wanted nil", test.name, fe) 807 } 808 }) 809 } 810 } 811 812 func TestAlsoStaysNil(t *testing.T) { 813 var err *FieldError 814 815 err = err.Also(nil) 816 if err != nil { 817 t.Errorf("Got: %v, want nil", err) 818 } 819 820 err = err.ViaField("nil").Also(nil) 821 if err != nil { 822 t.Errorf("Got: %v, want nil", err) 823 } 824 825 err = err.Also(&FieldError{}) 826 if err != nil { 827 t.Errorf("expected nil, got %v, wanted nil", err) 828 } 829 } 830 831 func TestAlsoMultiple(t *testing.T) { 832 var err *FieldError 833 834 errs := []*FieldError{{ 835 Message: "1", 836 Paths: []string{"bar"}, 837 }, { 838 Message: "2", 839 Paths: []string{"baz"}, 840 }} 841 err = err.Also(errs...) 842 const want = "1: bar\n2: baz" 843 if got := err.Error(); got != want { 844 t.Errorf("Got = %q, want: %q", got, want) 845 } 846 847 // This shows the order of Also calls does not matter. 848 err = nil 849 err = err.Also(errs[0]).Also(errs[1]) 850 if got := err.Error(); got != want { 851 t.Errorf("Got = %q, want: %q", got, want) 852 } 853 } 854 855 func TestFlatten(t *testing.T) { 856 tests := []struct { 857 name string 858 indices []string 859 want string 860 }{{ 861 name: "simple", 862 indices: strings.Split("foo.[1]", "."), 863 want: "foo[1]", 864 }, { 865 name: "no brackets", 866 indices: strings.Split("foo.bar", "."), 867 want: "foo.bar", 868 }, { 869 name: "err([0]).ViaField(bar).ViaField(foo)", 870 indices: strings.Split("foo.bar.[0]", "."), 871 want: "foo.bar[0]", 872 }, { 873 name: "err(bar).ViaIndex(0).ViaField(foo)", 874 indices: strings.Split("foo.[0].bar", "."), 875 want: "foo[0].bar", 876 }, { 877 name: "err(bar).ViaField(foo).ViaIndex(0)", 878 indices: strings.Split("[0].foo.bar", "."), 879 want: "[0].foo.bar", 880 }, { 881 name: "err(bar).ViaIndex(0).ViaIndex[1].ViaField(foo)", 882 indices: strings.Split("foo.[1].[0].bar", "."), 883 want: "foo[1][0].bar", 884 }, { 885 name: "err(foo).ViaField(bar).ViaIndex[0].ViaField(baz)", 886 indices: []string{"foo", "bar.[0].baz"}, 887 want: "foo.bar[0].baz", 888 }} 889 890 for _, test := range tests { 891 t.Run(test.name, func(t *testing.T) { 892 if got, want := flatten(test.indices), test.want; got != want { 893 t.Errorf("got: %q, want %q", got, want) 894 } 895 }) 896 } 897 } 898 899 func makeIndex(index string) int { 900 all := strings.Split(index, ",") 901 if i, err := strconv.Atoi(all[0]); err == nil { 902 return i 903 } 904 return -1 905 } 906 907 func makeFieldIndex(fi string) (string, int) { 908 all := strings.Split(fi, ",") 909 if i, err := strconv.Atoi(all[1]); err == nil { 910 return all[0], i 911 } 912 return "error", -1 913 } 914 915 func makeKey(key string) string { 916 all := strings.Split(key, ",") 917 return all[0] 918 } 919 920 func makeFieldKey(fk string) (string, string) { 921 all := strings.Split(fk, ",") 922 return all[0], all[1] 923 }