blob: 4767fd1dda104f0cb5eb296b380a9e5dd4656120 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
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 // 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.
125func (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).
150func 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).
157func 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).
163func 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).
169func 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).
176func 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).
192func 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.
200func 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.
207func 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.
214type 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.
218func 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.
228func (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
242func 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.
252func (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}