Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame^] | 1 | /* |
| 2 | Copyright 2014 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package field |
| 18 | |
| 19 | import ( |
| 20 | "fmt" |
| 21 | "reflect" |
| 22 | "strconv" |
| 23 | "strings" |
| 24 | |
| 25 | utilerrors "k8s.io/apimachinery/pkg/util/errors" |
| 26 | "k8s.io/apimachinery/pkg/util/sets" |
| 27 | ) |
| 28 | |
| 29 | // Error is an implementation of the 'error' interface, which represents a |
| 30 | // field-level validation error. |
| 31 | type Error struct { |
| 32 | Type ErrorType |
| 33 | Field string |
| 34 | BadValue interface{} |
| 35 | Detail string |
| 36 | } |
| 37 | |
| 38 | var _ error = &Error{} |
| 39 | |
| 40 | // Error implements the error interface. |
| 41 | func (v *Error) Error() string { |
| 42 | return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody()) |
| 43 | } |
| 44 | |
| 45 | // ErrorBody returns the error message without the field name. This is useful |
| 46 | // for building nice-looking higher-level error reporting. |
| 47 | func (v *Error) ErrorBody() string { |
| 48 | var s string |
| 49 | switch v.Type { |
| 50 | case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal: |
| 51 | s = v.Type.String() |
| 52 | default: |
| 53 | value := v.BadValue |
| 54 | valueType := reflect.TypeOf(value) |
| 55 | if value == nil || valueType == nil { |
| 56 | value = "null" |
| 57 | } else if valueType.Kind() == reflect.Ptr { |
| 58 | if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() { |
| 59 | value = "null" |
| 60 | } else { |
| 61 | value = reflectValue.Elem().Interface() |
| 62 | } |
| 63 | } |
| 64 | switch t := value.(type) { |
| 65 | case int64, int32, float64, float32, bool: |
| 66 | // use simple printer for simple types |
| 67 | s = fmt.Sprintf("%s: %v", v.Type, value) |
| 68 | case string: |
| 69 | s = fmt.Sprintf("%s: %q", v.Type, t) |
| 70 | case fmt.Stringer: |
| 71 | // anything that defines String() is better than raw struct |
| 72 | s = fmt.Sprintf("%s: %s", v.Type, t.String()) |
| 73 | default: |
| 74 | // fallback to raw struct |
| 75 | // TODO: internal types have panic guards against json.Marshalling to prevent |
| 76 | // accidental use of internal types in external serialized form. For now, use |
| 77 | // %#v, although it would be better to show a more expressive output in the future |
| 78 | s = fmt.Sprintf("%s: %#v", v.Type, value) |
| 79 | } |
| 80 | } |
| 81 | if len(v.Detail) != 0 { |
| 82 | s += fmt.Sprintf(": %s", v.Detail) |
| 83 | } |
| 84 | return s |
| 85 | } |
| 86 | |
| 87 | // ErrorType is a machine readable value providing more detail about why |
| 88 | // a field is invalid. These values are expected to match 1-1 with |
| 89 | // CauseType in api/types.go. |
| 90 | type ErrorType string |
| 91 | |
| 92 | // TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it. |
| 93 | const ( |
| 94 | // ErrorTypeNotFound is used to report failure to find a requested value |
| 95 | // (e.g. looking up an ID). See NotFound(). |
| 96 | ErrorTypeNotFound ErrorType = "FieldValueNotFound" |
| 97 | // ErrorTypeRequired is used to report required values that are not |
| 98 | // provided (e.g. empty strings, null values, or empty arrays). See |
| 99 | // Required(). |
| 100 | ErrorTypeRequired ErrorType = "FieldValueRequired" |
| 101 | // ErrorTypeDuplicate is used to report collisions of values that must be |
| 102 | // unique (e.g. unique IDs). See Duplicate(). |
| 103 | ErrorTypeDuplicate ErrorType = "FieldValueDuplicate" |
| 104 | // ErrorTypeInvalid is used to report malformed values (e.g. failed regex |
| 105 | // match, too long, out of bounds). See Invalid(). |
| 106 | ErrorTypeInvalid ErrorType = "FieldValueInvalid" |
| 107 | // ErrorTypeNotSupported is used to report unknown values for enumerated |
| 108 | // fields (e.g. a list of valid values). See NotSupported(). |
| 109 | ErrorTypeNotSupported ErrorType = "FieldValueNotSupported" |
| 110 | // ErrorTypeForbidden is used to report valid (as per formatting rules) |
| 111 | // values which would be accepted under some conditions, but which are not |
| 112 | // permitted by the current conditions (such as security policy). See |
| 113 | // Forbidden(). |
| 114 | ErrorTypeForbidden ErrorType = "FieldValueForbidden" |
| 115 | // ErrorTypeTooLong is used to report that the given value is too long. |
| 116 | // This is similar to ErrorTypeInvalid, but the error will not include the |
| 117 | // too-long value. See TooLong(). |
| 118 | ErrorTypeTooLong ErrorType = "FieldValueTooLong" |
| 119 | // ErrorTypeInternal is used to report other errors that are not related |
| 120 | // to user input. See InternalError(). |
| 121 | ErrorTypeInternal ErrorType = "InternalError" |
| 122 | ) |
| 123 | |
| 124 | // String converts a ErrorType into its corresponding canonical error message. |
| 125 | func (t ErrorType) String() string { |
| 126 | switch t { |
| 127 | case ErrorTypeNotFound: |
| 128 | return "Not found" |
| 129 | case ErrorTypeRequired: |
| 130 | return "Required value" |
| 131 | case ErrorTypeDuplicate: |
| 132 | return "Duplicate value" |
| 133 | case ErrorTypeInvalid: |
| 134 | return "Invalid value" |
| 135 | case ErrorTypeNotSupported: |
| 136 | return "Unsupported value" |
| 137 | case ErrorTypeForbidden: |
| 138 | return "Forbidden" |
| 139 | case ErrorTypeTooLong: |
| 140 | return "Too long" |
| 141 | case ErrorTypeInternal: |
| 142 | return "Internal error" |
| 143 | default: |
| 144 | panic(fmt.Sprintf("unrecognized validation error: %q", string(t))) |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // NotFound returns a *Error indicating "value not found". This is |
| 149 | // used to report failure to find a requested value (e.g. looking up an ID). |
| 150 | func NotFound(field *Path, value interface{}) *Error { |
| 151 | return &Error{ErrorTypeNotFound, field.String(), value, ""} |
| 152 | } |
| 153 | |
| 154 | // Required returns a *Error indicating "value required". This is used |
| 155 | // to report required values that are not provided (e.g. empty strings, null |
| 156 | // values, or empty arrays). |
| 157 | func Required(field *Path, detail string) *Error { |
| 158 | return &Error{ErrorTypeRequired, field.String(), "", detail} |
| 159 | } |
| 160 | |
| 161 | // Duplicate returns a *Error indicating "duplicate value". This is |
| 162 | // used to report collisions of values that must be unique (e.g. names or IDs). |
| 163 | func Duplicate(field *Path, value interface{}) *Error { |
| 164 | return &Error{ErrorTypeDuplicate, field.String(), value, ""} |
| 165 | } |
| 166 | |
| 167 | // Invalid returns a *Error indicating "invalid value". This is used |
| 168 | // to report malformed values (e.g. failed regex match, too long, out of bounds). |
| 169 | func Invalid(field *Path, value interface{}, detail string) *Error { |
| 170 | return &Error{ErrorTypeInvalid, field.String(), value, detail} |
| 171 | } |
| 172 | |
| 173 | // NotSupported returns a *Error indicating "unsupported value". |
| 174 | // This is used to report unknown values for enumerated fields (e.g. a list of |
| 175 | // valid values). |
| 176 | func NotSupported(field *Path, value interface{}, validValues []string) *Error { |
| 177 | detail := "" |
| 178 | if validValues != nil && len(validValues) > 0 { |
| 179 | quotedValues := make([]string, len(validValues)) |
| 180 | for i, v := range validValues { |
| 181 | quotedValues[i] = strconv.Quote(v) |
| 182 | } |
| 183 | detail = "supported values: " + strings.Join(quotedValues, ", ") |
| 184 | } |
| 185 | return &Error{ErrorTypeNotSupported, field.String(), value, detail} |
| 186 | } |
| 187 | |
| 188 | // Forbidden returns a *Error indicating "forbidden". This is used to |
| 189 | // report valid (as per formatting rules) values which would be accepted under |
| 190 | // some conditions, but which are not permitted by current conditions (e.g. |
| 191 | // security policy). |
| 192 | func Forbidden(field *Path, detail string) *Error { |
| 193 | return &Error{ErrorTypeForbidden, field.String(), "", detail} |
| 194 | } |
| 195 | |
| 196 | // TooLong returns a *Error indicating "too long". This is used to |
| 197 | // report that the given value is too long. This is similar to |
| 198 | // Invalid, but the returned error will not include the too-long |
| 199 | // value. |
| 200 | func TooLong(field *Path, value interface{}, maxLength int) *Error { |
| 201 | return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)} |
| 202 | } |
| 203 | |
| 204 | // InternalError returns a *Error indicating "internal error". This is used |
| 205 | // to signal that an error was found that was not directly related to user |
| 206 | // input. The err argument must be non-nil. |
| 207 | func InternalError(field *Path, err error) *Error { |
| 208 | return &Error{ErrorTypeInternal, field.String(), nil, err.Error()} |
| 209 | } |
| 210 | |
| 211 | // ErrorList holds a set of Errors. It is plausible that we might one day have |
| 212 | // non-field errors in this same umbrella package, but for now we don't, so |
| 213 | // we can keep it simple and leave ErrorList here. |
| 214 | type ErrorList []*Error |
| 215 | |
| 216 | // NewErrorTypeMatcher returns an errors.Matcher that returns true |
| 217 | // if the provided error is a Error and has the provided ErrorType. |
| 218 | func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher { |
| 219 | return func(err error) bool { |
| 220 | if e, ok := err.(*Error); ok { |
| 221 | return e.Type == t |
| 222 | } |
| 223 | return false |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | // ToAggregate converts the ErrorList into an errors.Aggregate. |
| 228 | func (list ErrorList) ToAggregate() utilerrors.Aggregate { |
| 229 | errs := make([]error, 0, len(list)) |
| 230 | errorMsgs := sets.NewString() |
| 231 | for _, err := range list { |
| 232 | msg := fmt.Sprintf("%v", err) |
| 233 | if errorMsgs.Has(msg) { |
| 234 | continue |
| 235 | } |
| 236 | errorMsgs.Insert(msg) |
| 237 | errs = append(errs, err) |
| 238 | } |
| 239 | return utilerrors.NewAggregate(errs) |
| 240 | } |
| 241 | |
| 242 | func fromAggregate(agg utilerrors.Aggregate) ErrorList { |
| 243 | errs := agg.Errors() |
| 244 | list := make(ErrorList, len(errs)) |
| 245 | for i := range errs { |
| 246 | list[i] = errs[i].(*Error) |
| 247 | } |
| 248 | return list |
| 249 | } |
| 250 | |
| 251 | // Filter removes items from the ErrorList that match the provided fns. |
| 252 | func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList { |
| 253 | err := utilerrors.FilterOut(list.ToAggregate(), fns...) |
| 254 | if err == nil { |
| 255 | return nil |
| 256 | } |
| 257 | // FilterOut takes an Aggregate and returns an Aggregate |
| 258 | return fromAggregate(err.(utilerrors.Aggregate)) |
| 259 | } |