blob: 0cd5d65775a700cbabd6eb2dac31e158528cec64 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package field
18
19import (
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.
31type Error struct {
32 Type ErrorType
33 Field string
34 BadValue interface{}
35 Detail string
36}
37
38var _ error = &Error{}
39
40// Error implements the error interface.
41func (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.
47func (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.
90type ErrorType string
91
92// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
93const (
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 // ErrorTypeTooMany is used to report "too many". This is used to
120 // report that a given list has too many items. This is similar to FieldValueTooLong,
121 // but the error indicates quantity instead of length.
122 ErrorTypeTooMany ErrorType = "FieldValueTooMany"
123 // ErrorTypeInternal is used to report other errors that are not related
124 // to user input. See InternalError().
125 ErrorTypeInternal ErrorType = "InternalError"
126)
127
128// String converts a ErrorType into its corresponding canonical error message.
129func (t ErrorType) String() string {
130 switch t {
131 case ErrorTypeNotFound:
132 return "Not found"
133 case ErrorTypeRequired:
134 return "Required value"
135 case ErrorTypeDuplicate:
136 return "Duplicate value"
137 case ErrorTypeInvalid:
138 return "Invalid value"
139 case ErrorTypeNotSupported:
140 return "Unsupported value"
141 case ErrorTypeForbidden:
142 return "Forbidden"
143 case ErrorTypeTooLong:
144 return "Too long"
145 case ErrorTypeTooMany:
146 return "Too many"
147 case ErrorTypeInternal:
148 return "Internal error"
149 default:
150 panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
151 }
152}
153
154// NotFound returns a *Error indicating "value not found". This is
155// used to report failure to find a requested value (e.g. looking up an ID).
156func NotFound(field *Path, value interface{}) *Error {
157 return &Error{ErrorTypeNotFound, field.String(), value, ""}
158}
159
160// Required returns a *Error indicating "value required". This is used
161// to report required values that are not provided (e.g. empty strings, null
162// values, or empty arrays).
163func Required(field *Path, detail string) *Error {
164 return &Error{ErrorTypeRequired, field.String(), "", detail}
165}
166
167// Duplicate returns a *Error indicating "duplicate value". This is
168// used to report collisions of values that must be unique (e.g. names or IDs).
169func Duplicate(field *Path, value interface{}) *Error {
170 return &Error{ErrorTypeDuplicate, field.String(), value, ""}
171}
172
173// Invalid returns a *Error indicating "invalid value". This is used
174// to report malformed values (e.g. failed regex match, too long, out of bounds).
175func Invalid(field *Path, value interface{}, detail string) *Error {
176 return &Error{ErrorTypeInvalid, field.String(), value, detail}
177}
178
179// NotSupported returns a *Error indicating "unsupported value".
180// This is used to report unknown values for enumerated fields (e.g. a list of
181// valid values).
182func NotSupported(field *Path, value interface{}, validValues []string) *Error {
183 detail := ""
184 if validValues != nil && len(validValues) > 0 {
185 quotedValues := make([]string, len(validValues))
186 for i, v := range validValues {
187 quotedValues[i] = strconv.Quote(v)
188 }
189 detail = "supported values: " + strings.Join(quotedValues, ", ")
190 }
191 return &Error{ErrorTypeNotSupported, field.String(), value, detail}
192}
193
194// Forbidden returns a *Error indicating "forbidden". This is used to
195// report valid (as per formatting rules) values which would be accepted under
196// some conditions, but which are not permitted by current conditions (e.g.
197// security policy).
198func Forbidden(field *Path, detail string) *Error {
199 return &Error{ErrorTypeForbidden, field.String(), "", detail}
200}
201
202// TooLong returns a *Error indicating "too long". This is used to
203// report that the given value is too long. This is similar to
204// Invalid, but the returned error will not include the too-long
205// value.
206func TooLong(field *Path, value interface{}, maxLength int) *Error {
207 return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d bytes", maxLength)}
208}
209
210// TooMany returns a *Error indicating "too many". This is used to
211// report that a given list has too many items. This is similar to TooLong,
212// but the returned error indicates quantity instead of length.
213func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
214 return &Error{ErrorTypeTooMany, field.String(), actualQuantity, fmt.Sprintf("must have at most %d items", maxQuantity)}
215}
216
217// InternalError returns a *Error indicating "internal error". This is used
218// to signal that an error was found that was not directly related to user
219// input. The err argument must be non-nil.
220func InternalError(field *Path, err error) *Error {
221 return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
222}
223
224// ErrorList holds a set of Errors. It is plausible that we might one day have
225// non-field errors in this same umbrella package, but for now we don't, so
226// we can keep it simple and leave ErrorList here.
227type ErrorList []*Error
228
229// NewErrorTypeMatcher returns an errors.Matcher that returns true
230// if the provided error is a Error and has the provided ErrorType.
231func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
232 return func(err error) bool {
233 if e, ok := err.(*Error); ok {
234 return e.Type == t
235 }
236 return false
237 }
238}
239
240// ToAggregate converts the ErrorList into an errors.Aggregate.
241func (list ErrorList) ToAggregate() utilerrors.Aggregate {
242 errs := make([]error, 0, len(list))
243 errorMsgs := sets.NewString()
244 for _, err := range list {
245 msg := fmt.Sprintf("%v", err)
246 if errorMsgs.Has(msg) {
247 continue
248 }
249 errorMsgs.Insert(msg)
250 errs = append(errs, err)
251 }
252 return utilerrors.NewAggregate(errs)
253}
254
255func fromAggregate(agg utilerrors.Aggregate) ErrorList {
256 errs := agg.Errors()
257 list := make(ErrorList, len(errs))
258 for i := range errs {
259 list[i] = errs[i].(*Error)
260 }
261 return list
262}
263
264// Filter removes items from the ErrorList that match the provided fns.
265func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
266 err := utilerrors.FilterOut(list.ToAggregate(), fns...)
267 if err == nil {
268 return nil
269 }
270 // FilterOut takes an Aggregate and returns an Aggregate
271 return fromAggregate(err.(utilerrors.Aggregate))
272}