1
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
30
31 type Error struct {
32 Type ErrorType
33 Field string
34 BadValue interface{}
35 Detail string
36 }
37
38 var _ error = &Error{}
39
40
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
50
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
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
84 s = fmt.Sprintf("%s: %s", v.Type, t.String())
85 default:
86
87
88
89
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
100
101
102 type ErrorType string
103
104
105 const (
106
107
108 ErrorTypeNotFound ErrorType = "FieldValueNotFound"
109
110
111
112 ErrorTypeRequired ErrorType = "FieldValueRequired"
113
114
115 ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
116
117
118 ErrorTypeInvalid ErrorType = "FieldValueInvalid"
119
120
121 ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
122
123
124
125
126 ErrorTypeForbidden ErrorType = "FieldValueForbidden"
127
128
129
130 ErrorTypeTooLong ErrorType = "FieldValueTooLong"
131
132
133
134 ErrorTypeTooMany ErrorType = "FieldValueTooMany"
135
136
137 ErrorTypeInternal ErrorType = "InternalError"
138
139 ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid"
140 )
141
142
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
171 func TypeInvalid(field *Path, value interface{}, detail string) *Error {
172 return &Error{ErrorTypeTypeInvalid, field.String(), value, detail}
173 }
174
175
176
177 func NotFound(field *Path, value interface{}) *Error {
178 return &Error{ErrorTypeNotFound, field.String(), value, ""}
179 }
180
181
182
183
184 func Required(field *Path, detail string) *Error {
185 return &Error{ErrorTypeRequired, field.String(), "", detail}
186 }
187
188
189
190 func Duplicate(field *Path, value interface{}) *Error {
191 return &Error{ErrorTypeDuplicate, field.String(), value, ""}
192 }
193
194
195
196 func Invalid(field *Path, value interface{}, detail string) *Error {
197 return &Error{ErrorTypeInvalid, field.String(), value, detail}
198 }
199
200
201
202
203 func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *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(fmt.Sprint(v))
209 }
210 detail = "supported values: " + strings.Join(quotedValues, ", ")
211 }
212 return &Error{ErrorTypeNotSupported, field.String(), value, detail}
213 }
214
215
216
217
218
219 func Forbidden(field *Path, detail string) *Error {
220 return &Error{ErrorTypeForbidden, field.String(), "", detail}
221 }
222
223
224
225
226
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
232
233
234
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
246
247
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
268
269
270 func InternalError(field *Path, err error) *Error {
271 return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
272 }
273
274
275
276
277 type ErrorList []*Error
278
279
280
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
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
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
324 return fromAggregate(err.(utilerrors.Aggregate))
325 }
326
View as plain text