2017-02-07 21:33:23 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
|
|
|
|
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 field
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-07 10:49:53 +00:00
|
|
|
utilerrors "k8s.io/client-go/pkg/util/errors"
|
2017-02-07 21:33:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Error is an implementation of the 'error' interface, which represents a
|
|
|
|
// field-level validation error.
|
|
|
|
type Error struct {
|
|
|
|
Type ErrorType
|
|
|
|
Field string
|
|
|
|
BadValue interface{}
|
|
|
|
Detail string
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ error = &Error{}
|
|
|
|
|
|
|
|
// Error implements the error interface.
|
|
|
|
func (v *Error) Error() string {
|
|
|
|
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorBody returns the error message without the field name. This is useful
|
|
|
|
// for building nice-looking higher-level error reporting.
|
|
|
|
func (v *Error) ErrorBody() string {
|
|
|
|
var s string
|
|
|
|
switch v.Type {
|
|
|
|
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
|
|
|
|
s = fmt.Sprintf("%s", v.Type)
|
|
|
|
default:
|
|
|
|
var bad string
|
|
|
|
badBytes, err := json.Marshal(v.BadValue)
|
|
|
|
if err != nil {
|
|
|
|
bad = err.Error()
|
|
|
|
} else {
|
|
|
|
bad = string(badBytes)
|
|
|
|
}
|
|
|
|
s = fmt.Sprintf("%s: %s", v.Type, bad)
|
|
|
|
}
|
|
|
|
if len(v.Detail) != 0 {
|
|
|
|
s += fmt.Sprintf(": %s", v.Detail)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorType 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 ErrorType string
|
|
|
|
|
|
|
|
// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
|
|
|
|
const (
|
|
|
|
// ErrorTypeNotFound is used to report failure to find a requested value
|
|
|
|
// (e.g. looking up an ID). See NotFound().
|
|
|
|
ErrorTypeNotFound ErrorType = "FieldValueNotFound"
|
|
|
|
// ErrorTypeRequired is used to report required values that are not
|
|
|
|
// provided (e.g. empty strings, null values, or empty arrays). See
|
|
|
|
// Required().
|
|
|
|
ErrorTypeRequired ErrorType = "FieldValueRequired"
|
|
|
|
// ErrorTypeDuplicate is used to report collisions of values that must be
|
|
|
|
// unique (e.g. unique IDs). See Duplicate().
|
|
|
|
ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
|
|
|
|
// ErrorTypeInvalid is used to report malformed values (e.g. failed regex
|
|
|
|
// match, too long, out of bounds). See Invalid().
|
|
|
|
ErrorTypeInvalid ErrorType = "FieldValueInvalid"
|
|
|
|
// ErrorTypeNotSupported is used to report unknown values for enumerated
|
|
|
|
// fields (e.g. a list of valid values). See NotSupported().
|
|
|
|
ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
|
|
|
|
// ErrorTypeForbidden is used to report valid (as per formatting rules)
|
|
|
|
// values which would be accepted under some conditions, but which are not
|
|
|
|
// permitted by the current conditions (such as security policy). See
|
|
|
|
// Forbidden().
|
|
|
|
ErrorTypeForbidden ErrorType = "FieldValueForbidden"
|
|
|
|
// ErrorTypeTooLong is used to report that the given value is too long.
|
|
|
|
// This is similar to ErrorTypeInvalid, but the error will not include the
|
|
|
|
// too-long value. See TooLong().
|
|
|
|
ErrorTypeTooLong ErrorType = "FieldValueTooLong"
|
|
|
|
// ErrorTypeInternal is used to report other errors that are not related
|
|
|
|
// to user input. See InternalError().
|
|
|
|
ErrorTypeInternal ErrorType = "InternalError"
|
|
|
|
)
|
|
|
|
|
|
|
|
// String converts a ErrorType into its corresponding canonical error message.
|
|
|
|
func (t ErrorType) String() string {
|
|
|
|
switch t {
|
|
|
|
case ErrorTypeNotFound:
|
|
|
|
return "Not found"
|
|
|
|
case ErrorTypeRequired:
|
|
|
|
return "Required value"
|
|
|
|
case ErrorTypeDuplicate:
|
|
|
|
return "Duplicate value"
|
|
|
|
case ErrorTypeInvalid:
|
|
|
|
return "Invalid value"
|
|
|
|
case ErrorTypeNotSupported:
|
|
|
|
return "Unsupported value"
|
|
|
|
case ErrorTypeForbidden:
|
|
|
|
return "Forbidden"
|
|
|
|
case ErrorTypeTooLong:
|
|
|
|
return "Too long"
|
|
|
|
case ErrorTypeInternal:
|
|
|
|
return "Internal error"
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotFound 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 NotFound(field *Path, value interface{}) *Error {
|
|
|
|
return &Error{ErrorTypeNotFound, field.String(), value, ""}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Required 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 Required(field *Path, detail string) *Error {
|
|
|
|
return &Error{ErrorTypeRequired, field.String(), "", detail}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate returns a *Error indicating "duplicate value". This is
|
|
|
|
// used to report collisions of values that must be unique (e.g. names or IDs).
|
|
|
|
func Duplicate(field *Path, value interface{}) *Error {
|
|
|
|
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalid returns a *Error indicating "invalid value". This is used
|
|
|
|
// to report malformed values (e.g. failed regex match, too long, out of bounds).
|
|
|
|
func Invalid(field *Path, value interface{}, detail string) *Error {
|
|
|
|
return &Error{ErrorTypeInvalid, field.String(), value, detail}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotSupported returns a *Error indicating "unsupported value".
|
|
|
|
// This is used to report unknown values for enumerated fields (e.g. a list of
|
|
|
|
// valid values).
|
|
|
|
func NotSupported(field *Path, value interface{}, validValues []string) *Error {
|
|
|
|
detail := ""
|
|
|
|
if validValues != nil && len(validValues) > 0 {
|
|
|
|
detail = "supported values: " + strings.Join(validValues, ", ")
|
|
|
|
}
|
|
|
|
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forbidden 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 Forbidden(field *Path, detail string) *Error {
|
|
|
|
return &Error{ErrorTypeForbidden, field.String(), "", detail}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TooLong returns a *Error indicating "too long". This is used to
|
|
|
|
// report that the given value is too long. This is similar to
|
|
|
|
// Invalid, but the returned error will not include the too-long
|
|
|
|
// value.
|
|
|
|
func TooLong(field *Path, value interface{}, maxLength int) *Error {
|
|
|
|
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// InternalError 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 InternalError(field *Path, err error) *Error {
|
|
|
|
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorList holds a set of Errors. It is plausible that we might one day have
|
|
|
|
// non-field errors in this same umbrella package, but for now we don't, so
|
|
|
|
// we can keep it simple and leave ErrorList here.
|
|
|
|
type ErrorList []*Error
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
return func(err error) bool {
|
|
|
|
if e, ok := err.(*Error); ok {
|
|
|
|
return e.Type == t
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToAggregate converts the ErrorList into an errors.Aggregate.
|
|
|
|
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
|
|
|
|
errs := make([]error, len(list))
|
|
|
|
for i := range list {
|
|
|
|
errs[i] = list[i]
|
|
|
|
}
|
|
|
|
return utilerrors.NewAggregate(errs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func fromAggregate(agg utilerrors.Aggregate) ErrorList {
|
|
|
|
errs := agg.Errors()
|
|
|
|
list := make(ErrorList, len(errs))
|
|
|
|
for i := range errs {
|
|
|
|
list[i] = errs[i].(*Error)
|
|
|
|
}
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter removes items from the ErrorList that match the provided fns.
|
|
|
|
func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
|
|
|
|
err := utilerrors.FilterOut(list.ToAggregate(), fns...)
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// FilterOut takes an Aggregate and returns an Aggregate
|
|
|
|
return fromAggregate(err.(utilerrors.Aggregate))
|
|
|
|
}
|