Merge pull request #17248 from thockin/airplane_validation_pt4

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot
2015-12-03 16:59:23 -08:00
21 changed files with 1201 additions and 890 deletions

View File

@@ -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 {

View File

@@ -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)
}
}
}

View 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()
}

View 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())
}
}
}