mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
Merge pull request #17248 from thockin/airplane_validation_pt4
Auto commit by PR queue bot
This commit is contained in:
@@ -121,88 +121,66 @@ func (t ErrorType) String() string {
|
||||
|
||||
// NewNotFoundError returns a *Error indicating "value not found". This is
|
||||
// used to report failure to find a requested value (e.g. looking up an ID).
|
||||
func NewNotFoundError(field string, value interface{}) *Error {
|
||||
return &Error{ErrorTypeNotFound, field, value, ""}
|
||||
func NewNotFoundError(field *FieldPath, value interface{}) *Error {
|
||||
return &Error{ErrorTypeNotFound, field.String(), value, ""}
|
||||
}
|
||||
|
||||
// NewRequiredError returns a *Error indicating "value required". This is used
|
||||
// to report required values that are not provided (e.g. empty strings, null
|
||||
// values, or empty arrays).
|
||||
func NewRequiredError(field string) *Error {
|
||||
return &Error{ErrorTypeRequired, field, "", ""}
|
||||
func NewRequiredError(field *FieldPath) *Error {
|
||||
return &Error{ErrorTypeRequired, field.String(), "", ""}
|
||||
}
|
||||
|
||||
// NewDuplicateError returns a *Error indicating "duplicate value". This is
|
||||
// used to report collisions of values that must be unique (e.g. names or IDs).
|
||||
func NewDuplicateError(field string, value interface{}) *Error {
|
||||
return &Error{ErrorTypeDuplicate, field, value, ""}
|
||||
func NewDuplicateError(field *FieldPath, value interface{}) *Error {
|
||||
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
|
||||
}
|
||||
|
||||
// NewInvalidError returns a *Error indicating "invalid value". This is used
|
||||
// to report malformed values (e.g. failed regex match, too long, out of bounds).
|
||||
func NewInvalidError(field string, value interface{}, detail string) *Error {
|
||||
return &Error{ErrorTypeInvalid, field, value, detail}
|
||||
func NewInvalidError(field *FieldPath, value interface{}, detail string) *Error {
|
||||
return &Error{ErrorTypeInvalid, field.String(), value, detail}
|
||||
}
|
||||
|
||||
// NewNotSupportedError returns a *Error indicating "unsupported value".
|
||||
// This is used to report unknown values for enumerated fields (e.g. a list of
|
||||
// valid values).
|
||||
func NewNotSupportedError(field string, value interface{}, validValues []string) *Error {
|
||||
func NewNotSupportedError(field *FieldPath, value interface{}, validValues []string) *Error {
|
||||
detail := ""
|
||||
if validValues != nil && len(validValues) > 0 {
|
||||
detail = "supported values: " + strings.Join(validValues, ", ")
|
||||
}
|
||||
return &Error{ErrorTypeNotSupported, field, value, detail}
|
||||
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
|
||||
}
|
||||
|
||||
// NewForbiddenError returns a *Error indicating "forbidden". This is used to
|
||||
// report valid (as per formatting rules) values which would be accepted under
|
||||
// some conditions, but which are not permitted by current conditions (e.g.
|
||||
// security policy).
|
||||
func NewForbiddenError(field string, value interface{}) *Error {
|
||||
return &Error{ErrorTypeForbidden, field, value, ""}
|
||||
func NewForbiddenError(field *FieldPath, value interface{}) *Error {
|
||||
return &Error{ErrorTypeForbidden, field.String(), value, ""}
|
||||
}
|
||||
|
||||
// NewTooLongError returns a *Error indicating "too long". This is used to
|
||||
// report that the given value is too long. This is similar to
|
||||
// NewInvalidError, but the returned error will not include the too-long
|
||||
// value.
|
||||
func NewTooLongError(field string, value interface{}, maxLength int) *Error {
|
||||
return &Error{ErrorTypeTooLong, field, value, fmt.Sprintf("must have at most %d characters", maxLength)}
|
||||
func NewTooLongError(field *FieldPath, value interface{}, maxLength int) *Error {
|
||||
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)}
|
||||
}
|
||||
|
||||
// NewInternalError returns a *Error indicating "internal error". This is used
|
||||
// to signal that an error was found that was not directly related to user
|
||||
// input. The err argument must be non-nil.
|
||||
func NewInternalError(field string, err error) *Error {
|
||||
return &Error{ErrorTypeInternal, field, nil, err.Error()}
|
||||
func NewInternalError(field *FieldPath, err error) *Error {
|
||||
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
|
||||
}
|
||||
|
||||
// ErrorList holds a set of errors.
|
||||
type ErrorList []*Error
|
||||
|
||||
// Prefix adds a prefix to the Field of every Error in the list.
|
||||
// Returns the list for convenience.
|
||||
func (list ErrorList) Prefix(prefix string) ErrorList {
|
||||
for i := range list {
|
||||
err := list[i]
|
||||
if strings.HasPrefix(err.Field, "[") {
|
||||
err.Field = prefix + err.Field
|
||||
} else if len(err.Field) != 0 {
|
||||
err.Field = prefix + "." + err.Field
|
||||
} else {
|
||||
err.Field = prefix
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PrefixIndex adds an index to the Field of every Error in the list.
|
||||
// Returns the list for convenience.
|
||||
func (list ErrorList) PrefixIndex(index int) ErrorList {
|
||||
return list.Prefix(fmt.Sprintf("[%d]", index))
|
||||
}
|
||||
|
||||
// NewErrorTypeMatcher returns an errors.Matcher that returns true
|
||||
// if the provided error is a Error and has the provided ErrorType.
|
||||
func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
|
||||
|
||||
@@ -28,27 +28,27 @@ func TestMakeFuncs(t *testing.T) {
|
||||
expected ErrorType
|
||||
}{
|
||||
{
|
||||
func() *Error { return NewInvalidError("f", "v", "d") },
|
||||
func() *Error { return NewInvalidError(NewFieldPath("f"), "v", "d") },
|
||||
ErrorTypeInvalid,
|
||||
},
|
||||
{
|
||||
func() *Error { return NewNotSupportedError("f", "v", nil) },
|
||||
func() *Error { return NewNotSupportedError(NewFieldPath("f"), "v", nil) },
|
||||
ErrorTypeNotSupported,
|
||||
},
|
||||
{
|
||||
func() *Error { return NewDuplicateError("f", "v") },
|
||||
func() *Error { return NewDuplicateError(NewFieldPath("f"), "v") },
|
||||
ErrorTypeDuplicate,
|
||||
},
|
||||
{
|
||||
func() *Error { return NewNotFoundError("f", "v") },
|
||||
func() *Error { return NewNotFoundError(NewFieldPath("f"), "v") },
|
||||
ErrorTypeNotFound,
|
||||
},
|
||||
{
|
||||
func() *Error { return NewRequiredError("f") },
|
||||
func() *Error { return NewRequiredError(NewFieldPath("f")) },
|
||||
ErrorTypeRequired,
|
||||
},
|
||||
{
|
||||
func() *Error { return NewInternalError("f", fmt.Errorf("e")) },
|
||||
func() *Error { return NewInternalError(NewFieldPath("f"), fmt.Errorf("e")) },
|
||||
ErrorTypeInternal,
|
||||
},
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func TestMakeFuncs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrorUsefulMessage(t *testing.T) {
|
||||
s := NewInvalidError("foo", "bar", "deet").Error()
|
||||
s := NewInvalidError(NewFieldPath("foo"), "bar", "deet").Error()
|
||||
t.Logf("message: %v", s)
|
||||
for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} {
|
||||
if !strings.Contains(s, part) {
|
||||
@@ -77,7 +77,7 @@ func TestErrorUsefulMessage(t *testing.T) {
|
||||
KV map[string]int
|
||||
}
|
||||
s = NewInvalidError(
|
||||
"foo",
|
||||
NewFieldPath("foo"),
|
||||
&complicated{
|
||||
Baz: 1,
|
||||
Qux: "aoeu",
|
||||
@@ -102,8 +102,8 @@ func TestToAggregate(t *testing.T) {
|
||||
testCases := []ErrorList{
|
||||
nil,
|
||||
{},
|
||||
{NewInvalidError("f", "v", "d")},
|
||||
{NewInvalidError("f", "v", "d"), NewInternalError("", fmt.Errorf("e"))},
|
||||
{NewInvalidError(NewFieldPath("f"), "v", "d")},
|
||||
{NewInvalidError(NewFieldPath("f"), "v", "d"), NewInternalError(NewFieldPath(""), fmt.Errorf("e"))},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
agg := tc.ToAggregate()
|
||||
@@ -121,9 +121,9 @@ func TestToAggregate(t *testing.T) {
|
||||
|
||||
func TestErrListFilter(t *testing.T) {
|
||||
list := ErrorList{
|
||||
NewInvalidError("test.field", "", ""),
|
||||
NewInvalidError("field.test", "", ""),
|
||||
NewDuplicateError("test", "value"),
|
||||
NewInvalidError(NewFieldPath("test.field"), "", ""),
|
||||
NewInvalidError(NewFieldPath("field.test"), "", ""),
|
||||
NewDuplicateError(NewFieldPath("test"), "value"),
|
||||
}
|
||||
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 {
|
||||
t.Errorf("should not filter")
|
||||
@@ -132,63 +132,3 @@ func TestErrListFilter(t *testing.T) {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListPrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Err *Error
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
NewNotFoundError("[0].bar", "value"),
|
||||
"foo[0].bar",
|
||||
},
|
||||
{
|
||||
NewInvalidError("field", "value", ""),
|
||||
"foo.field",
|
||||
},
|
||||
{
|
||||
NewDuplicateError("", "value"),
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
errList := ErrorList{testCase.Err}
|
||||
prefix := errList.Prefix("foo")
|
||||
if prefix == nil || len(prefix) != len(errList) {
|
||||
t.Errorf("Prefix should return self")
|
||||
}
|
||||
if e, a := testCase.Expected, errList[0].Field; e != a {
|
||||
t.Errorf("expected %s, got %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListPrefixIndex(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Err *Error
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
NewNotFoundError("[0].bar", "value"),
|
||||
"[1][0].bar",
|
||||
},
|
||||
{
|
||||
NewInvalidError("field", "value", ""),
|
||||
"[1].field",
|
||||
},
|
||||
{
|
||||
NewDuplicateError("", "value"),
|
||||
"[1]",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
errList := ErrorList{testCase.Err}
|
||||
prefix := errList.PrefixIndex(1)
|
||||
if prefix == nil || len(prefix) != len(errList) {
|
||||
t.Errorf("PrefixIndex should return self")
|
||||
}
|
||||
if e, a := testCase.Expected, errList[0].Field; e != a {
|
||||
t.Errorf("expected %s, got %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
pkg/util/validation/fieldpath.go
Normal file
91
pkg/util/validation/fieldpath.go
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// FieldPath represents the path from some root to a particular field.
|
||||
type FieldPath struct {
|
||||
name string // the name of this field or "" if this is an index
|
||||
index string // if name == "", this is a subscript (index or map key) of the previous element
|
||||
parent *FieldPath // nil if this is the root element
|
||||
}
|
||||
|
||||
// NewFieldPath creates a root FieldPath object.
|
||||
func NewFieldPath(name string, moreNames ...string) *FieldPath {
|
||||
r := &FieldPath{name: name, parent: nil}
|
||||
for _, anotherName := range moreNames {
|
||||
r = &FieldPath{name: anotherName, parent: r}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Root returns the root element of this FieldPath.
|
||||
func (fp *FieldPath) Root() *FieldPath {
|
||||
for ; fp.parent != nil; fp = fp.parent {
|
||||
// Do nothing.
|
||||
}
|
||||
return fp
|
||||
}
|
||||
|
||||
// Child creates a new FieldPath that is a child of the method receiver.
|
||||
func (fp *FieldPath) Child(name string, moreNames ...string) *FieldPath {
|
||||
r := NewFieldPath(name, moreNames...)
|
||||
r.Root().parent = fp
|
||||
return r
|
||||
}
|
||||
|
||||
// Index indicates that the previous FieldPath is to be subscripted by an int.
|
||||
// This sets the same underlying value as Key.
|
||||
func (fp *FieldPath) Index(index int) *FieldPath {
|
||||
return &FieldPath{index: strconv.Itoa(index), parent: fp}
|
||||
}
|
||||
|
||||
// Key indicates that the previous FieldPath is to be subscripted by a string.
|
||||
// This sets the same underlying value as Index.
|
||||
func (fp *FieldPath) Key(key string) *FieldPath {
|
||||
return &FieldPath{index: key, parent: fp}
|
||||
}
|
||||
|
||||
// String produces a string representation of the FieldPath.
|
||||
func (fp *FieldPath) String() string {
|
||||
// make a slice to iterate
|
||||
elems := []*FieldPath{}
|
||||
for p := fp; p != nil; p = p.parent {
|
||||
elems = append(elems, p)
|
||||
}
|
||||
|
||||
// iterate, but it has to be backwards
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i := range elems {
|
||||
p := elems[len(elems)-1-i]
|
||||
if p.parent != nil && len(p.name) > 0 {
|
||||
// This is either the root or it is a subscript.
|
||||
buf.WriteString(".")
|
||||
}
|
||||
if len(p.name) > 0 {
|
||||
buf.WriteString(p.name)
|
||||
} else {
|
||||
fmt.Fprintf(buf, "[%s]", p.index)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
123
pkg/util/validation/fieldpath_test.go
Normal file
123
pkg/util/validation/fieldpath_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFieldPath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
op func(*FieldPath) *FieldPath
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp },
|
||||
"root",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Child("first") },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Child("second") },
|
||||
"root.first.second",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Index(0) },
|
||||
"root.first.second[0]",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Child("third") },
|
||||
"root.first.second[0].third",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Index(93) },
|
||||
"root.first.second[0].third[93]",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root.first.second[0].third",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root.first.second[0]",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Key("key") },
|
||||
"root.first.second[0][key]",
|
||||
},
|
||||
}
|
||||
|
||||
root := NewFieldPath("root")
|
||||
fp := root
|
||||
for i, tc := range testCases {
|
||||
fp = tc.op(fp)
|
||||
if fp.String() != tc.expected {
|
||||
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, fp.String())
|
||||
}
|
||||
if fp.Root() != root {
|
||||
t.Errorf("[%d] Wrong root: %#v", i, fp.Root())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldPathMultiArg(t *testing.T) {
|
||||
testCases := []struct {
|
||||
op func(*FieldPath) *FieldPath
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Child("second", "third") },
|
||||
"root.first.second.third",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.Index(0) },
|
||||
"root.first.second.third[0]",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root.first.second.third",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root.first.second",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(fp *FieldPath) *FieldPath { return fp.parent },
|
||||
"root",
|
||||
},
|
||||
}
|
||||
|
||||
root := NewFieldPath("root", "first")
|
||||
fp := root
|
||||
for i, tc := range testCases {
|
||||
fp = tc.op(fp)
|
||||
if fp.String() != tc.expected {
|
||||
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, fp.String())
|
||||
}
|
||||
if fp.Root() != root.Root() {
|
||||
t.Errorf("[%d] Wrong root: %#v", i, fp.Root())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user