mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-16 14:45:28 +00:00
Move field errors to pkg/util/fielderrors
Allows pkg/api to take a reference to labels.Selector and fields.Selector
This commit is contained in:
200
pkg/util/fielderrors/fielderrors.go
Normal file
200
pkg/util/fielderrors/fielderrors.go
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. 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 fielderrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// ValidationErrorType is a machine readable value providing more detail about why
|
||||
// a field is invalid. These values are expected to match 1-1 with
|
||||
// CauseType in api/types.go.
|
||||
type ValidationErrorType string
|
||||
|
||||
// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
|
||||
const (
|
||||
// ValidationErrorTypeNotFound is used to report failure to find a requested value
|
||||
// (e.g. looking up an ID).
|
||||
ValidationErrorTypeNotFound ValidationErrorType = "FieldValueNotFound"
|
||||
// ValidationErrorTypeRequired is used to report required values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays).
|
||||
ValidationErrorTypeRequired ValidationErrorType = "FieldValueRequired"
|
||||
// ValidationErrorTypeDuplicate is used to report collisions of values that must be
|
||||
// unique (e.g. unique IDs).
|
||||
ValidationErrorTypeDuplicate ValidationErrorType = "FieldValueDuplicate"
|
||||
// ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex
|
||||
// match).
|
||||
ValidationErrorTypeInvalid ValidationErrorType = "FieldValueInvalid"
|
||||
// ValidationErrorTypeNotSupported is used to report valid (as per formatting rules)
|
||||
// values that can not be handled (e.g. an enumerated string).
|
||||
ValidationErrorTypeNotSupported ValidationErrorType = "FieldValueNotSupported"
|
||||
// ValidationErrorTypeForbidden is used to report valid (as per formatting rules)
|
||||
// values which would be accepted by some api instances, but which would invoke behavior
|
||||
// not permitted by this api instance (such as due to stricter security policy).
|
||||
ValidationErrorTypeForbidden ValidationErrorType = "FieldValueForbidden"
|
||||
// ValidationErrorTypeTooLong is used to report that given value is too long.
|
||||
ValidationErrorTypeTooLong ValidationErrorType = "FieldValueTooLong"
|
||||
)
|
||||
|
||||
// String converts a ValidationErrorType into its corresponding error message.
|
||||
func (t ValidationErrorType) String() string {
|
||||
switch t {
|
||||
case ValidationErrorTypeNotFound:
|
||||
return "not found"
|
||||
case ValidationErrorTypeRequired:
|
||||
return "required value"
|
||||
case ValidationErrorTypeDuplicate:
|
||||
return "duplicate value"
|
||||
case ValidationErrorTypeInvalid:
|
||||
return "invalid value"
|
||||
case ValidationErrorTypeNotSupported:
|
||||
return "unsupported value"
|
||||
case ValidationErrorTypeForbidden:
|
||||
return "forbidden"
|
||||
case ValidationErrorTypeTooLong:
|
||||
return "too long"
|
||||
default:
|
||||
glog.Errorf("unrecognized validation type: %#v", t)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// ValidationError is an implementation of the 'error' interface, which represents an error of validation.
|
||||
type ValidationError struct {
|
||||
Type ValidationErrorType
|
||||
Field string
|
||||
BadValue interface{}
|
||||
Detail string
|
||||
}
|
||||
|
||||
var _ error = &ValidationError{}
|
||||
|
||||
func (v *ValidationError) Error() string {
|
||||
var s string
|
||||
switch v.Type {
|
||||
case ValidationErrorTypeRequired:
|
||||
s = spew.Sprintf("%s: %s", v.Field, v.Type)
|
||||
default:
|
||||
s = spew.Sprintf("%s: %s '%+v'", v.Field, v.Type, v.BadValue)
|
||||
}
|
||||
if len(v.Detail) != 0 {
|
||||
s += fmt.Sprintf(": %s", v.Detail)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewFieldRequired returns a *ValidationError indicating "value required"
|
||||
func NewFieldRequired(field string) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeRequired, field, "", ""}
|
||||
}
|
||||
|
||||
// NewFieldInvalid returns a *ValidationError indicating "invalid value"
|
||||
func NewFieldInvalid(field string, value interface{}, detail string) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeInvalid, field, value, detail}
|
||||
}
|
||||
|
||||
// NewFieldNotSupported returns a *ValidationError indicating "unsupported value"
|
||||
func NewFieldNotSupported(field string, value interface{}) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeNotSupported, field, value, ""}
|
||||
}
|
||||
|
||||
// NewFieldForbidden returns a *ValidationError indicating "forbidden"
|
||||
func NewFieldForbidden(field string, value interface{}) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeForbidden, field, value, ""}
|
||||
}
|
||||
|
||||
// NewFieldDuplicate returns a *ValidationError indicating "duplicate value"
|
||||
func NewFieldDuplicate(field string, value interface{}) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeDuplicate, field, value, ""}
|
||||
}
|
||||
|
||||
// NewFieldNotFound returns a *ValidationError indicating "value not found"
|
||||
func NewFieldNotFound(field string, value interface{}) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeNotFound, field, value, ""}
|
||||
}
|
||||
|
||||
func NewFieldTooLong(field string, value interface{}) *ValidationError {
|
||||
return &ValidationError{ValidationErrorTypeTooLong, field, value, ""}
|
||||
}
|
||||
|
||||
type ValidationErrorList []error
|
||||
|
||||
// Prefix adds a prefix to the Field of every ValidationError in the list.
|
||||
// Returns the list for convenience.
|
||||
func (list ValidationErrorList) Prefix(prefix string) ValidationErrorList {
|
||||
for i := range list {
|
||||
if err, ok := list[i].(*ValidationError); ok {
|
||||
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
|
||||
}
|
||||
list[i] = err
|
||||
} else {
|
||||
glog.Warningf("Programmer error: ValidationErrorList holds non-ValidationError: %#v", list[i])
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PrefixIndex adds an index to the Field of every ValidationError in the list.
|
||||
// Returns the list for convenience.
|
||||
func (list ValidationErrorList) PrefixIndex(index int) ValidationErrorList {
|
||||
return list.Prefix(fmt.Sprintf("[%d]", index))
|
||||
}
|
||||
|
||||
// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
|
||||
// if the provided error is a ValidationError and has the provided ValidationErrorType.
|
||||
func NewValidationErrorTypeMatcher(t ValidationErrorType) errors.Matcher {
|
||||
return func(err error) bool {
|
||||
if e, ok := err.(*ValidationError); ok {
|
||||
return e.Type == t
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
|
||||
// if the provided error is a ValidationError and has a field with the provided
|
||||
// prefix.
|
||||
func NewValidationErrorFieldPrefixMatcher(prefix string) errors.Matcher {
|
||||
return func(err error) bool {
|
||||
if e, ok := err.(*ValidationError); ok {
|
||||
return strings.HasPrefix(e.Field, prefix)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Filter removes items from the ValidationErrorList that match the provided fns.
|
||||
func (list ValidationErrorList) Filter(fns ...errors.Matcher) ValidationErrorList {
|
||||
err := errors.FilterOut(errors.NewAggregate(list), fns...)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// FilterOut that takes an Aggregate returns an Aggregate
|
||||
agg := err.(errors.Aggregate)
|
||||
return ValidationErrorList(agg.Errors())
|
||||
}
|
180
pkg/util/fielderrors/fielderrors_test.go
Normal file
180
pkg/util/fielderrors/fielderrors_test.go
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. 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 fielderrors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMakeFuncs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
fn func() *ValidationError
|
||||
expected ValidationErrorType
|
||||
}{
|
||||
{
|
||||
func() *ValidationError { return NewFieldInvalid("f", "v", "d") },
|
||||
ValidationErrorTypeInvalid,
|
||||
},
|
||||
{
|
||||
func() *ValidationError { return NewFieldNotSupported("f", "v") },
|
||||
ValidationErrorTypeNotSupported,
|
||||
},
|
||||
{
|
||||
func() *ValidationError { return NewFieldDuplicate("f", "v") },
|
||||
ValidationErrorTypeDuplicate,
|
||||
},
|
||||
{
|
||||
func() *ValidationError { return NewFieldNotFound("f", "v") },
|
||||
ValidationErrorTypeNotFound,
|
||||
},
|
||||
{
|
||||
func() *ValidationError { return NewFieldRequired("f") },
|
||||
ValidationErrorTypeRequired,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := testCase.fn()
|
||||
if err.Type != testCase.expected {
|
||||
t.Errorf("expected Type %q, got %q", testCase.expected, err.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidationErrorUsefulMessage(t *testing.T) {
|
||||
s := NewFieldInvalid("foo", "bar", "deet").Error()
|
||||
t.Logf("message: %v", s)
|
||||
for _, part := range []string{"foo", "bar", "deet", ValidationErrorTypeInvalid.String()} {
|
||||
if !strings.Contains(s, part) {
|
||||
t.Errorf("error message did not contain expected part '%v'", part)
|
||||
}
|
||||
}
|
||||
|
||||
type complicated struct {
|
||||
Baz int
|
||||
Qux string
|
||||
Inner interface{}
|
||||
KV map[string]int
|
||||
}
|
||||
s = NewFieldInvalid(
|
||||
"foo",
|
||||
&complicated{
|
||||
Baz: 1,
|
||||
Qux: "aoeu",
|
||||
Inner: &complicated{Qux: "asdf"},
|
||||
KV: map[string]int{"Billy": 2},
|
||||
},
|
||||
"detail",
|
||||
).Error()
|
||||
t.Logf("message: %v", s)
|
||||
for _, part := range []string{
|
||||
"foo", ValidationErrorTypeInvalid.String(),
|
||||
"Baz", "Qux", "Inner", "KV", "detail",
|
||||
"1", "aoeu", "asdf", "Billy", "2",
|
||||
} {
|
||||
if !strings.Contains(s, part) {
|
||||
t.Errorf("error message did not contain expected part '%v'", part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListFilter(t *testing.T) {
|
||||
list := ValidationErrorList{
|
||||
NewFieldInvalid("test.field", "", ""),
|
||||
NewFieldInvalid("field.test", "", ""),
|
||||
NewFieldDuplicate("test", "value"),
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorTypeMatcher(ValidationErrorTypeDuplicate))) != 2 {
|
||||
t.Errorf("should not filter")
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorTypeMatcher(ValidationErrorTypeInvalid))) != 1 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorFieldPrefixMatcher("test"))) != 1 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorFieldPrefixMatcher("test."))) != 2 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorFieldPrefixMatcher(""))) != 0 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
if len(list.Filter(NewValidationErrorFieldPrefixMatcher("field."), NewValidationErrorTypeMatcher(ValidationErrorTypeDuplicate))) != 1 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListPrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Err *ValidationError
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
NewFieldNotFound("[0].bar", "value"),
|
||||
"foo[0].bar",
|
||||
},
|
||||
{
|
||||
NewFieldInvalid("field", "value", ""),
|
||||
"foo.field",
|
||||
},
|
||||
{
|
||||
NewFieldDuplicate("", "value"),
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
errList := ValidationErrorList{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].(*ValidationError).Field; e != a {
|
||||
t.Errorf("expected %s, got %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListPrefixIndex(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Err *ValidationError
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
NewFieldNotFound("[0].bar", "value"),
|
||||
"[1][0].bar",
|
||||
},
|
||||
{
|
||||
NewFieldInvalid("field", "value", ""),
|
||||
"[1].field",
|
||||
},
|
||||
{
|
||||
NewFieldDuplicate("", "value"),
|
||||
"[1]",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
errList := ValidationErrorList{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].(*ValidationError).Field; e != a {
|
||||
t.Errorf("expected %s, got %s", e, a)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user