diff --git a/pkg/util/validation/errors.go b/pkg/util/validation/errors.go index e8f9b0783ff..d439f35961f 100644 --- a/pkg/util/validation/errors.go +++ b/pkg/util/validation/errors.go @@ -25,6 +25,38 @@ import ( "github.com/davecgh/go-spew/spew" ) +// Error is an implementation of the 'error' interface, which represents a +// 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, ErrorTypeTooLong: + s = spew.Sprintf("%s", v.Type) + default: + s = spew.Sprintf("%s '%+v'", v.Type, v.BadValue) + } + if len(v.Detail) != 0 { + s += fmt.Sprintf(", Details: %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. @@ -32,30 +64,35 @@ type ErrorType string // TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it. const ( - // ErrorType is used to report failure to find a requested value - // (e.g. looking up an ID). + // ErrorTypeNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). See NewFieldNotFound. ErrorTypeNotFound ErrorType = "FieldValueNotFound" // ErrorTypeRequired is used to report required values that are not - // provided (e.g. empty strings, null values, or empty arrays). + // provided (e.g. empty strings, null values, or empty arrays). See + // NewFieldRequired. ErrorTypeRequired ErrorType = "FieldValueRequired" // ErrorTypeDuplicate is used to report collisions of values that must be - // unique (e.g. unique IDs). + // unique (e.g. unique IDs). See NewFieldDuplicate. ErrorTypeDuplicate ErrorType = "FieldValueDuplicate" // ErrorTypeInvalid is used to report malformed values (e.g. failed regex - // match). + // match, too long, out of bounds). See NewFieldInvalid. ErrorTypeInvalid ErrorType = "FieldValueInvalid" // ErrorTypeNotSupported is used to report valid (as per formatting rules) - // values that can not be handled (e.g. an enumerated string). + // values that can not be handled (e.g. an enumerated string). See + // NewFieldValueNotSupported. ErrorTypeNotSupported ErrorType = "FieldValueNotSupported" // ErrorTypeForbidden 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). + // values which would be accepted under some conditions, but which are not + // permitted by the current conditions (such as security policy). See + // NewFieldForbidden. ErrorTypeForbidden ErrorType = "FieldValueForbidden" - // ErrorTypeTooLong is used to report that given value is too long. + // 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 NewFieldTooLong. ErrorTypeTooLong ErrorType = "FieldValueTooLong" ) -// String converts a ErrorType into its corresponding error message. +// String converts a ErrorType into its corresponding canonical error message. func (t ErrorType) String() string { switch t { case ErrorTypeNotFound: @@ -73,50 +110,39 @@ func (t ErrorType) String() string { case ErrorTypeTooLong: return "too long" default: - panic(fmt.Sprintf("unrecognized validation type: %#v", t)) + panic(fmt.Sprintf("unrecognized validation error: %q", t)) return "" } } -// Error is an implementation of the 'error' interface, which represents an error of validation. -type Error struct { - Type ErrorType - Field string - BadValue interface{} - Detail string +// NewFieldNotFound 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 NewFieldNotFound(field string, value interface{}) *Error { + return &Error{ErrorTypeNotFound, field, value, ""} } -var _ error = &Error{} - -func (v *Error) Error() string { - return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody()) -} - -func (v *Error) ErrorBody() string { - var s string - switch v.Type { - case ErrorTypeRequired, ErrorTypeTooLong: - s = spew.Sprintf("%s", v.Type) - default: - s = spew.Sprintf("%s '%+v'", v.Type, v.BadValue) - } - if len(v.Detail) != 0 { - s += fmt.Sprintf(", Details: %s", v.Detail) - } - return s -} - -// NewFieldRequired returns a *Error indicating "value required" +// NewFieldRequired 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 NewFieldRequired(field string) *Error { return &Error{ErrorTypeRequired, field, "", ""} } -// NewFieldInvalid returns a *Error indicating "invalid value" +// NewFieldDuplicate returns a *Error indicating "duplicate value". This is +// used to report collisions of values that must be unique (e.g. names or IDs). +func NewFieldDuplicate(field string, value interface{}) *Error { + return &Error{ErrorTypeDuplicate, field, value, ""} +} + +// NewFieldInvalid returns a *Error indicating "invalid value". This is used +// to report malformed values (e.g. failed regex match, too long, out of bounds). func NewFieldInvalid(field string, value interface{}, detail string) *Error { return &Error{ErrorTypeInvalid, field, value, detail} } -// NewFieldValueNotSupported returns a *Error indicating "unsupported value" +// NewFieldValueNotSupported returns a *Error indicating "unsupported value". +// This is used to report valid (as per formatting rules) values that can not +// be handled (e.g. an enumerated string). func NewFieldValueNotSupported(field string, value interface{}, validValues []string) *Error { detail := "" if validValues != nil && len(validValues) > 0 { @@ -125,25 +151,23 @@ func NewFieldValueNotSupported(field string, value interface{}, validValues []st return &Error{ErrorTypeNotSupported, field, value, detail} } -// NewFieldForbidden returns a *Error indicating "forbidden" +// NewFieldForbidden 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 NewFieldForbidden(field string, value interface{}) *Error { return &Error{ErrorTypeForbidden, field, value, ""} } -// NewFieldDuplicate returns a *Error indicating "duplicate value" -func NewFieldDuplicate(field string, value interface{}) *Error { - return &Error{ErrorTypeDuplicate, field, value, ""} -} - -// NewFieldNotFound returns a *Error indicating "value not found" -func NewFieldNotFound(field string, value interface{}) *Error { - return &Error{ErrorTypeNotFound, field, value, ""} -} - +// NewFieldTooLong returns a *Error indicating "too long". This is used to +// report that the given value is too long. This is similar to +// NewFieldInvalid, but the returned error will not include the too-long +// value. func NewFieldTooLong(field string, value interface{}, maxLength int) *Error { return &Error{ErrorTypeTooLong, field, value, fmt.Sprintf("must have at most %d characters", maxLength)} } +// ErrorList holds a set of errors. type ErrorList []error // Prefix adds a prefix to the Field of every Error in the list.