github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/validation/field/errors.go (about) 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 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/errors" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/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 type OmitValueType struct{} 46 47 var omitValue = OmitValueType{} 48 49 // ErrorBody returns the error message without the field name. This is useful 50 // for building nice-looking higher-level error reporting. 51 func (v *Error) ErrorBody() string { 52 var s string 53 switch { 54 case v.Type == ErrorTypeRequired: 55 s = v.Type.String() 56 case v.Type == ErrorTypeForbidden: 57 s = v.Type.String() 58 case v.Type == ErrorTypeTooLong: 59 s = v.Type.String() 60 case v.Type == ErrorTypeInternal: 61 s = v.Type.String() 62 case v.BadValue == omitValue: 63 s = v.Type.String() 64 default: 65 value := v.BadValue 66 valueType := reflect.TypeOf(value) 67 if value == nil || valueType == nil { 68 value = "null" 69 } else if valueType.Kind() == reflect.Pointer { 70 if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() { 71 value = "null" 72 } else { 73 value = reflectValue.Elem().Interface() 74 } 75 } 76 switch t := value.(type) { 77 case int64, int32, float64, float32, bool: 78 // use simple printer for simple types 79 s = fmt.Sprintf("%s: %v", v.Type, value) 80 case string: 81 s = fmt.Sprintf("%s: %q", v.Type, t) 82 case fmt.Stringer: 83 // anything that defines String() is better than raw struct 84 s = fmt.Sprintf("%s: %s", v.Type, t.String()) 85 default: 86 // fallback to raw struct 87 // TODO: internal types have panic guards against json.Marshalling to prevent 88 // accidental use of internal types in external serialized form. For now, use 89 // %#v, although it would be better to show a more expressive output in the future 90 s = fmt.Sprintf("%s: %#v", v.Type, value) 91 } 92 } 93 if len(v.Detail) != 0 { 94 s += fmt.Sprintf(": %s", v.Detail) 95 } 96 return s 97 } 98 99 // ErrorType is a machine readable value providing more detail about why 100 // a field is invalid. These values are expected to match 1-1 with 101 // CauseType in api/types.go. 102 type ErrorType string 103 104 // TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it. 105 const ( 106 // ErrorTypeNotFound is used to report failure to find a requested value 107 // (e.g. looking up an ID). See NotFound(). 108 ErrorTypeNotFound ErrorType = "FieldValueNotFound" 109 // ErrorTypeRequired is used to report required values that are not 110 // provided (e.g. empty strings, null values, or empty arrays). See 111 // Required(). 112 ErrorTypeRequired ErrorType = "FieldValueRequired" 113 // ErrorTypeDuplicate is used to report collisions of values that must be 114 // unique (e.g. unique IDs). See Duplicate(). 115 ErrorTypeDuplicate ErrorType = "FieldValueDuplicate" 116 // ErrorTypeInvalid is used to report malformed values (e.g. failed regex 117 // match, too long, out of bounds). See Invalid(). 118 ErrorTypeInvalid ErrorType = "FieldValueInvalid" 119 // ErrorTypeNotSupported is used to report unknown values for enumerated 120 // fields (e.g. a list of valid values). See NotSupported(). 121 ErrorTypeNotSupported ErrorType = "FieldValueNotSupported" 122 // ErrorTypeForbidden is used to report valid (as per formatting rules) 123 // values which would be accepted under some conditions, but which are not 124 // permitted by the current conditions (such as security policy). See 125 // Forbidden(). 126 ErrorTypeForbidden ErrorType = "FieldValueForbidden" 127 // ErrorTypeTooLong is used to report that the given value is too long. 128 // This is similar to ErrorTypeInvalid, but the error will not include the 129 // too-long value. See TooLong(). 130 ErrorTypeTooLong ErrorType = "FieldValueTooLong" 131 // ErrorTypeTooMany is used to report "too many". This is used to 132 // report that a given list has too many items. This is similar to FieldValueTooLong, 133 // but the error indicates quantity instead of length. 134 ErrorTypeTooMany ErrorType = "FieldValueTooMany" 135 // ErrorTypeInternal is used to report other errors that are not related 136 // to user input. See InternalError(). 137 ErrorTypeInternal ErrorType = "InternalError" 138 // ErrorTypeTypeInvalid is for the value did not match the schema type for that field 139 ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid" 140 ) 141 142 // String converts a ErrorType into its corresponding canonical error message. 143 func (t ErrorType) String() string { 144 switch t { 145 case ErrorTypeNotFound: 146 return "Not found" 147 case ErrorTypeRequired: 148 return "Required value" 149 case ErrorTypeDuplicate: 150 return "Duplicate value" 151 case ErrorTypeInvalid: 152 return "Invalid value" 153 case ErrorTypeNotSupported: 154 return "Unsupported value" 155 case ErrorTypeForbidden: 156 return "Forbidden" 157 case ErrorTypeTooLong: 158 return "Too long" 159 case ErrorTypeTooMany: 160 return "Too many" 161 case ErrorTypeInternal: 162 return "Internal error" 163 case ErrorTypeTypeInvalid: 164 return "Invalid value" 165 default: 166 panic(fmt.Sprintf("unrecognized validation error: %q", string(t))) 167 } 168 } 169 170 // TypeInvalid returns a *Error indicating "type is invalid" 171 func TypeInvalid(field *Path, value interface{}, detail string) *Error { 172 return &Error{ErrorTypeTypeInvalid, field.String(), value, detail} 173 } 174 175 // NotFound returns a *Error indicating "value not found". This is 176 // used to report failure to find a requested value (e.g. looking up an ID). 177 func NotFound(field *Path, value interface{}) *Error { 178 return &Error{ErrorTypeNotFound, field.String(), value, ""} 179 } 180 181 // Required returns a *Error indicating "value required". This is used 182 // to report required values that are not provided (e.g. empty strings, null 183 // values, or empty arrays). 184 func Required(field *Path, detail string) *Error { 185 return &Error{ErrorTypeRequired, field.String(), "", detail} 186 } 187 188 // Duplicate returns a *Error indicating "duplicate value". This is 189 // used to report collisions of values that must be unique (e.g. names or IDs). 190 func Duplicate(field *Path, value interface{}) *Error { 191 return &Error{ErrorTypeDuplicate, field.String(), value, ""} 192 } 193 194 // Invalid returns a *Error indicating "invalid value". This is used 195 // to report malformed values (e.g. failed regex match, too long, out of bounds). 196 func Invalid(field *Path, value interface{}, detail string) *Error { 197 return &Error{ErrorTypeInvalid, field.String(), value, detail} 198 } 199 200 // NotSupported returns a *Error indicating "unsupported value". 201 // This is used to report unknown values for enumerated fields (e.g. a list of 202 // valid values). 203 func NotSupported(field *Path, value interface{}, validValues []string) *Error { 204 detail := "" 205 if len(validValues) > 0 { 206 quotedValues := make([]string, len(validValues)) 207 for i, v := range validValues { 208 quotedValues[i] = strconv.Quote(v) 209 } 210 detail = "supported values: " + strings.Join(quotedValues, ", ") 211 } 212 return &Error{ErrorTypeNotSupported, field.String(), value, detail} 213 } 214 215 // Forbidden returns a *Error indicating "forbidden". This is used to 216 // report valid (as per formatting rules) values which would be accepted under 217 // some conditions, but which are not permitted by current conditions (e.g. 218 // security policy). 219 func Forbidden(field *Path, detail string) *Error { 220 return &Error{ErrorTypeForbidden, field.String(), "", detail} 221 } 222 223 // TooLong returns a *Error indicating "too long". This is used to 224 // report that the given value is too long. This is similar to 225 // Invalid, but the returned error will not include the too-long 226 // value. 227 func TooLong(field *Path, value interface{}, maxLength int) *Error { 228 return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d bytes", maxLength)} 229 } 230 231 // TooLongMaxLength returns a *Error indicating "too long". This is used to 232 // report that the given value is too long. This is similar to 233 // Invalid, but the returned error will not include the too-long 234 // value. If maxLength is negative, no max length will be included in the message. 235 func TooLongMaxLength(field *Path, value interface{}, maxLength int) *Error { 236 var msg string 237 if maxLength >= 0 { 238 msg = fmt.Sprintf("may not be longer than %d", maxLength) 239 } else { 240 msg = "value is too long" 241 } 242 return &Error{ErrorTypeTooLong, field.String(), value, msg} 243 } 244 245 // TooMany returns a *Error indicating "too many". This is used to 246 // report that a given list has too many items. This is similar to TooLong, 247 // but the returned error indicates quantity instead of length. 248 func TooMany(field *Path, actualQuantity, maxQuantity int) *Error { 249 var msg string 250 251 if maxQuantity >= 0 { 252 msg = fmt.Sprintf("must have at most %d items", maxQuantity) 253 } else { 254 msg = "has too many items" 255 } 256 257 var actual interface{} 258 if actualQuantity >= 0 { 259 actual = actualQuantity 260 } else { 261 actual = omitValue 262 } 263 264 return &Error{ErrorTypeTooMany, field.String(), actual, msg} 265 } 266 267 // InternalError returns a *Error indicating "internal error". This is used 268 // to signal that an error was found that was not directly related to user 269 // input. The err argument must be non-nil. 270 func InternalError(field *Path, err error) *Error { 271 return &Error{ErrorTypeInternal, field.String(), nil, err.Error()} 272 } 273 274 // ErrorList holds a set of Errors. It is plausible that we might one day have 275 // non-field errors in this same umbrella package, but for now we don't, so 276 // we can keep it simple and leave ErrorList here. 277 type ErrorList []*Error 278 279 // NewErrorTypeMatcher returns an errors.Matcher that returns true 280 // if the provided error is a Error and has the provided ErrorType. 281 func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher { 282 return func(err error) bool { 283 if e, ok := err.(*Error); ok { 284 return e.Type == t 285 } 286 return false 287 } 288 } 289 290 // ToAggregate converts the ErrorList into an errors.Aggregate. 291 func (list ErrorList) ToAggregate() utilerrors.Aggregate { 292 if len(list) == 0 { 293 return nil 294 } 295 errs := make([]error, 0, len(list)) 296 errorMsgs := sets.NewString() 297 for _, err := range list { 298 msg := fmt.Sprintf("%v", err) 299 if errorMsgs.Has(msg) { 300 continue 301 } 302 errorMsgs.Insert(msg) 303 errs = append(errs, err) 304 } 305 return utilerrors.NewAggregate(errs) 306 } 307 308 func fromAggregate(agg utilerrors.Aggregate) ErrorList { 309 errs := agg.Errors() 310 list := make(ErrorList, len(errs)) 311 for i := range errs { 312 list[i] = errs[i].(*Error) 313 } 314 return list 315 } 316 317 // Filter removes items from the ErrorList that match the provided fns. 318 func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList { 319 err := utilerrors.FilterOut(list.ToAggregate(), fns...) 320 if err == nil { 321 return nil 322 } 323 // FilterOut takes an Aggregate and returns an Aggregate 324 return fromAggregate(err.(utilerrors.Aggregate)) 325 }