mirror of https://github.com/knative/pkg.git
Adding ViaIndex, ViaKey, ViaFieldIndex, ViaFieldKey to FieldErrors (#55)
* Adding .ViaIndex(1). to make spec.param[1] easier to do. * Adding ViaKey * fix order of bag example. * Add code coverage to missed line. * Adding ViaFieldIndex helper * cleaning up. * compress more. * more cleanup * test name
This commit is contained in:
parent
2a7e950c4e
commit
f0ec8c5ac7
|
@ -52,17 +52,76 @@ func (fe *FieldError) ViaField(prefix ...string) *FieldError {
|
|||
}
|
||||
var newPaths []string
|
||||
for _, oldPath := range fe.Paths {
|
||||
if oldPath == CurrentField {
|
||||
newPaths = append(newPaths, strings.Join(prefix, "."))
|
||||
} else {
|
||||
newPaths = append(newPaths,
|
||||
strings.Join(append(prefix, oldPath), "."))
|
||||
}
|
||||
newPaths = append(newPaths, flatten(append(prefix, oldPath)))
|
||||
}
|
||||
fe.Paths = newPaths
|
||||
return fe
|
||||
}
|
||||
|
||||
// ViaIndex is used to attach an index to the next ViaField provided.
|
||||
// For example, if a type recursively validates a parameter that has a collection:
|
||||
// for i, c := range spec.Collection {
|
||||
// if err := doValidation(c); err != nil {
|
||||
// return err.ViaIndex(i).ViaField("collection")
|
||||
// }
|
||||
// }
|
||||
func (fe *FieldError) ViaIndex(index int) *FieldError {
|
||||
if fe == nil {
|
||||
return nil
|
||||
}
|
||||
return fe.ViaField(fmt.Sprintf("[%d]", index))
|
||||
}
|
||||
|
||||
// ViaFieldIndex is the short way to chain: err.ViaIndex(bar).ViaField(foo)
|
||||
func (fe *FieldError) ViaFieldIndex(field string, index int) *FieldError {
|
||||
return fe.ViaIndex(index).ViaField(field)
|
||||
}
|
||||
|
||||
// ViaKey is used to attach a key to the next ViaField provided.
|
||||
// For example, if a type recursively validates a parameter that has a collection:
|
||||
// for k, v := range spec.Bag. {
|
||||
// if err := doValidation(v); err != nil {
|
||||
// return err.ViaKey(k).ViaField("bag")
|
||||
// }
|
||||
// }
|
||||
func (fe *FieldError) ViaKey(key string) *FieldError {
|
||||
if fe == nil {
|
||||
return nil
|
||||
}
|
||||
return fe.ViaField(fmt.Sprintf("[%s]", key))
|
||||
}
|
||||
|
||||
// ViaFieldKey is the short way to chain: err.ViaKey(bar).ViaField(foo)
|
||||
func (fe *FieldError) ViaFieldKey(field string, key string) *FieldError {
|
||||
return fe.ViaKey(key).ViaField(field)
|
||||
}
|
||||
|
||||
// flatten takes in a array of path components and looks for chances to flatten
|
||||
// objects that have index prefixes, examples:
|
||||
// err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0]
|
||||
// err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar
|
||||
// err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar
|
||||
// err(bar).ViaIndex(0).ViaIndex[1].ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar
|
||||
func flatten(path []string) string {
|
||||
var newPath []string
|
||||
for _, part := range path {
|
||||
for _, p := range strings.Split(part, ".") {
|
||||
if p == CurrentField {
|
||||
continue
|
||||
} else if len(newPath) > 0 && isIndex(p) {
|
||||
newPath[len(newPath)-1] = fmt.Sprintf("%s%s", newPath[len(newPath)-1], p)
|
||||
} else {
|
||||
newPath = append(newPath, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(newPath, ".")
|
||||
}
|
||||
|
||||
func isIndex(part string) bool {
|
||||
return strings.HasPrefix(part, "[") && strings.HasSuffix(part, "]")
|
||||
}
|
||||
|
||||
// Error implements error
|
||||
func (fe *FieldError) Error() string {
|
||||
if fe.Details == "" {
|
||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
package apis
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -111,19 +113,19 @@ Body.`,
|
|||
err: ErrMultipleOneOf("foo", "bar"),
|
||||
prefixes: [][]string{{"baz"}},
|
||||
want: `expected exactly one, got both: baz.foo, baz.bar`,
|
||||
},{
|
||||
name: "invalid key name",
|
||||
err: ErrInvalidKeyName("b@r", "foo[0].name",
|
||||
}, {
|
||||
name: "invalid key name",
|
||||
err: ErrInvalidKeyName("b@r", "foo[0].name",
|
||||
"can not use @", "do not try"),
|
||||
prefixes: [][]string{{"baz"}},
|
||||
want: `invalid key name "b@r": baz.foo[0].name
|
||||
want: `invalid key name "b@r": baz.foo[0].name
|
||||
can not use @, do not try`,
|
||||
},{
|
||||
name: "invalid key name with details array",
|
||||
err: ErrInvalidKeyName("b@r", "foo[0].name",
|
||||
}, {
|
||||
name: "invalid key name with details array",
|
||||
err: ErrInvalidKeyName("b@r", "foo[0].name",
|
||||
[]string{"can not use @", "do not try"}...),
|
||||
prefixes: [][]string{{"baz"}},
|
||||
want: `invalid key name "b@r": baz.foo[0].name
|
||||
want: `invalid key name "b@r": baz.foo[0].name
|
||||
can not use @, do not try`,
|
||||
}}
|
||||
|
||||
|
@ -145,3 +147,263 @@ can not use @, do not try`,
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViaIndexOrKeyFieldError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err *FieldError
|
||||
prefixes [][]string
|
||||
want string
|
||||
}{{
|
||||
name: "simple single no propagation",
|
||||
err: &FieldError{
|
||||
Message: "hear me roar",
|
||||
Paths: []string{"bar"},
|
||||
},
|
||||
prefixes: [][]string{{"INDEX:3", "INDEX:2", "INDEX:1", "foo"}},
|
||||
want: "hear me roar: foo[1][2][3].bar",
|
||||
}, {
|
||||
name: "simple key",
|
||||
err: &FieldError{
|
||||
Message: "hear me roar",
|
||||
Paths: []string{"bar"},
|
||||
},
|
||||
prefixes: [][]string{{"KEY:C", "KEY:B", "KEY:A", "foo"}},
|
||||
want: "hear me roar: foo[A][B][C].bar",
|
||||
}, {
|
||||
name: "missing field propagation",
|
||||
err: ErrMissingField("foo", "bar"),
|
||||
prefixes: [][]string{{"[2]", "baz"}},
|
||||
want: "missing field(s): baz[2].foo, baz[2].bar",
|
||||
}, {
|
||||
name: "invalid key name",
|
||||
err: ErrInvalidKeyName("b@r", "name",
|
||||
"can not use @", "do not try"),
|
||||
prefixes: [][]string{{"baz", "INDEX:0", "foo"}},
|
||||
want: `invalid key name "b@r": foo[0].baz.name
|
||||
can not use @, do not try`,
|
||||
}, {
|
||||
name: "invalid key name with keys",
|
||||
err: ErrInvalidKeyName("b@r", "name",
|
||||
"can not use @", "do not try"),
|
||||
prefixes: [][]string{{"baz", "INDEX:0", "foo"}, {"bar", "KEY:A", "boo"}},
|
||||
want: `invalid key name "b@r": boo[A].bar.foo[0].baz.name
|
||||
can not use @, do not try`,
|
||||
}, {
|
||||
name: "multi prefixes provided",
|
||||
err: &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
},
|
||||
prefixes: [][]string{{"INDEX:2"}, {"bee"}, {"INDEX:0"}, {"baa", "baz", "ugh"}},
|
||||
want: "invalid field(s): ugh.baz.baa[0].bee[2].foo",
|
||||
}, {
|
||||
name: "use helper viaFieldIndex",
|
||||
err: &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
},
|
||||
prefixes: [][]string{{"FIELDINDEX:bee,2"}, {"FIELDINDEX:baa,0"}, {"baz", "ugh"}},
|
||||
want: "invalid field(s): ugh.baz.baa[0].bee[2].foo",
|
||||
}, {
|
||||
name: "use helper viaFieldKey",
|
||||
err: &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
},
|
||||
prefixes: [][]string{{"FIELDKEY:bee,AAA"}, {"FIELDKEY:baa,BBB"}, {"baz", "ugh"}},
|
||||
want: "invalid field(s): ugh.baz.baa[BBB].bee[AAA].foo",
|
||||
}, {
|
||||
name: "bypass helpers",
|
||||
err: &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
},
|
||||
prefixes: [][]string{{"[2]"}, {"[1]"}, {"bar"}},
|
||||
want: "invalid field(s): bar[1][2].foo",
|
||||
}, {
|
||||
name: "multi paths provided",
|
||||
err: &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo", "bar"},
|
||||
},
|
||||
prefixes: [][]string{{"INDEX:0"}, {"index"}, {"KEY:A"}, {"map"}},
|
||||
want: "invalid field(s): map[A].index[0].foo, map[A].index[0].bar",
|
||||
}, {
|
||||
name: "manual index",
|
||||
err: func() *FieldError {
|
||||
// Example, return an error in a loop:
|
||||
// for i, item := spec.myList {
|
||||
// err := item.validate().ViaIndex(i).ViaField("myList")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// --> I expect path to be myList[i].foo
|
||||
|
||||
err := &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
}
|
||||
|
||||
err = err.ViaIndex(0).ViaField("bar")
|
||||
err = err.ViaIndex(2).ViaIndex(1).ViaField("baz")
|
||||
err = err.ViaIndex(3).ViaIndex(4).ViaField("boof")
|
||||
return err
|
||||
}(),
|
||||
want: "invalid field(s): boof[4][3].baz[1][2].bar[0].foo",
|
||||
}, {
|
||||
name: "manual multiple index",
|
||||
err: func() *FieldError {
|
||||
|
||||
err := &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
}
|
||||
|
||||
err = err.ViaField("bear", "[1]", "[2]", "[3]", "baz", "]xxx[").ViaField("bar")
|
||||
return err
|
||||
}(),
|
||||
want: "invalid field(s): bar.bear[1][2][3].baz.]xxx[.foo",
|
||||
}, {
|
||||
name: "manual keys",
|
||||
err: func() *FieldError {
|
||||
err := &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo"},
|
||||
}
|
||||
|
||||
err = err.ViaKey("A").ViaField("bar")
|
||||
err = err.ViaKey("CCC").ViaKey("BB").ViaField("baz")
|
||||
err = err.ViaKey("E").ViaKey("F").ViaField("jar")
|
||||
return err
|
||||
}(),
|
||||
want: "invalid field(s): jar[F][E].baz[BB][CCC].bar[A].foo",
|
||||
}, {
|
||||
name: "manual index and keys",
|
||||
err: func() *FieldError {
|
||||
err := &FieldError{
|
||||
Message: "invalid field(s)",
|
||||
Paths: []string{"foo", "faa"},
|
||||
}
|
||||
|
||||
err = err.ViaKey("A").ViaField("bar")
|
||||
err = err.ViaIndex(1).ViaField("baz")
|
||||
err = err.ViaKey("E").ViaIndex(0).ViaField("jar")
|
||||
return err
|
||||
}(),
|
||||
want: "invalid field(s): jar[0][E].baz[1].bar[A].foo, jar[0][E].baz[1].bar[A].faa",
|
||||
}, {
|
||||
name: "nil propagation",
|
||||
err: nil,
|
||||
prefixes: [][]string{{"baz", "ugh", "INDEX:0", "KEY:A"}},
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fe := test.err
|
||||
// Simulate propagation up a call stack.
|
||||
for _, prefix := range test.prefixes {
|
||||
for _, p := range prefix {
|
||||
if strings.HasPrefix(p, "INDEX") {
|
||||
index := strings.Split(p, ":")
|
||||
fe = fe.ViaIndex(makeIndex(index[1]))
|
||||
} else if strings.HasPrefix(p, "FIELDINDEX") {
|
||||
index := strings.Split(p, ":")
|
||||
fe = fe.ViaFieldIndex(makeFieldIndex(index[1]))
|
||||
} else if strings.HasPrefix(p, "KEY") {
|
||||
key := strings.Split(p, ":")
|
||||
fe = fe.ViaKey(makeKey(key[1]))
|
||||
} else if strings.HasPrefix(p, "FIELDKEY") {
|
||||
index := strings.Split(p, ":")
|
||||
fe = fe.ViaFieldKey(makeFieldKey(index[1]))
|
||||
} else {
|
||||
fe = fe.ViaField(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if test.want != "" {
|
||||
got := fe.Error()
|
||||
if got != test.want {
|
||||
t.Errorf("Error() = %v, wanted %v", got, test.want)
|
||||
}
|
||||
} else if fe != nil {
|
||||
t.Errorf("ViaField() = %v, wanted nil", fe)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
indices []string
|
||||
want string
|
||||
}{{
|
||||
name: "simple",
|
||||
indices: strings.Split("foo.[1]", "."),
|
||||
want: "foo[1]",
|
||||
}, {
|
||||
name: "no brackets",
|
||||
indices: strings.Split("foo.bar", "."),
|
||||
want: "foo.bar",
|
||||
}, {
|
||||
name: "err([0]).ViaField(bar).ViaField(foo)",
|
||||
indices: strings.Split("foo.bar.[0]", "."),
|
||||
want: "foo.bar[0]",
|
||||
}, {
|
||||
name: "err(bar).ViaIndex(0).ViaField(foo)",
|
||||
indices: strings.Split("foo.[0].bar", "."),
|
||||
want: "foo[0].bar",
|
||||
}, {
|
||||
name: "err(bar).ViaField(foo).ViaIndex(0)",
|
||||
indices: strings.Split("[0].foo.bar", "."),
|
||||
want: "[0].foo.bar",
|
||||
}, {
|
||||
name: "err(bar).ViaIndex(0).ViaIndex[1].ViaField(foo)",
|
||||
indices: strings.Split("foo.[1].[0].bar", "."),
|
||||
want: "foo[1][0].bar",
|
||||
}, {
|
||||
name: "err(bar).ViaIndex(0).ViaIndex[1].ViaField(foo)",
|
||||
indices: []string{"foo", "bar.[0].baz"},
|
||||
want: "foo.bar[0].baz",
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
got := flatten(test.indices)
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("got: %q, want %q", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeIndex(index string) int {
|
||||
all := strings.Split(index, ",")
|
||||
if i, err := strconv.Atoi(all[0]); err == nil {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func makeFieldIndex(fi string) (string, int) {
|
||||
all := strings.Split(fi, ",")
|
||||
if i, err := strconv.Atoi(all[1]); err == nil {
|
||||
return all[0], i
|
||||
}
|
||||
return "error", -1
|
||||
}
|
||||
|
||||
func makeKey(key string) string {
|
||||
all := strings.Split(key, ",")
|
||||
return all[0]
|
||||
}
|
||||
|
||||
func makeFieldKey(fk string) (string, string) {
|
||||
all := strings.Split(fk, ",")
|
||||
return all[0], all[1]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue