@@ -33,6 +33,20 @@ type Error struct {
33
33
Field string
34
34
BadValue interface {}
35
35
Detail string
36
+
37
+ // Origin uniquely identifies where this error was generated from. It is used in testing to
38
+ // compare expected errors against actual errors without relying on exact detail string matching.
39
+ // This allows tests to verify the correct validation logic triggered the error
40
+ // regardless of how the error message might be formatted or localized.
41
+ //
42
+ // The value should be either:
43
+ // - A simple camelCase identifier (e.g., "maximum", "maxItems")
44
+ // - A structured format using "format=<dash-style-identifier>" for validation errors related to specific formats
45
+ // (e.g., "format=dns-label", "format=qualified-name")
46
+ //
47
+ // Origin should be set in the most deeply nested validation function that
48
+ // can still identify the unique source of the error.
49
+ Origin string
36
50
}
37
51
38
52
var _ error = & Error {}
@@ -96,6 +110,12 @@ func (v *Error) ErrorBody() string {
96
110
return s
97
111
}
98
112
113
+ // WithOrigin adds origin information to the FieldError
114
+ func (v * Error ) WithOrigin (o string ) * Error {
115
+ v .Origin = o
116
+ return v
117
+ }
118
+
99
119
// ErrorType is a machine readable value providing more detail about why
100
120
// a field is invalid. These values are expected to match 1-1 with
101
121
// CauseType in api/types.go.
@@ -169,32 +189,32 @@ func (t ErrorType) String() string {
169
189
170
190
// TypeInvalid returns a *Error indicating "type is invalid"
171
191
func TypeInvalid (field * Path , value interface {}, detail string ) * Error {
172
- return & Error {ErrorTypeTypeInvalid , field .String (), value , detail }
192
+ return & Error {ErrorTypeTypeInvalid , field .String (), value , detail , "" }
173
193
}
174
194
175
195
// NotFound returns a *Error indicating "value not found". This is
176
196
// used to report failure to find a requested value (e.g. looking up an ID).
177
197
func NotFound (field * Path , value interface {}) * Error {
178
- return & Error {ErrorTypeNotFound , field .String (), value , "" }
198
+ return & Error {ErrorTypeNotFound , field .String (), value , "" , "" }
179
199
}
180
200
181
201
// Required returns a *Error indicating "value required". This is used
182
202
// to report required values that are not provided (e.g. empty strings, null
183
203
// values, or empty arrays).
184
204
func Required (field * Path , detail string ) * Error {
185
- return & Error {ErrorTypeRequired , field .String (), "" , detail }
205
+ return & Error {ErrorTypeRequired , field .String (), "" , detail , "" }
186
206
}
187
207
188
208
// Duplicate returns a *Error indicating "duplicate value". This is
189
209
// used to report collisions of values that must be unique (e.g. names or IDs).
190
210
func Duplicate (field * Path , value interface {}) * Error {
191
- return & Error {ErrorTypeDuplicate , field .String (), value , "" }
211
+ return & Error {ErrorTypeDuplicate , field .String (), value , "" , "" }
192
212
}
193
213
194
214
// Invalid returns a *Error indicating "invalid value". This is used
195
215
// to report malformed values (e.g. failed regex match, too long, out of bounds).
196
216
func Invalid (field * Path , value interface {}, detail string ) * Error {
197
- return & Error {ErrorTypeInvalid , field .String (), value , detail }
217
+ return & Error {ErrorTypeInvalid , field .String (), value , detail , "" }
198
218
}
199
219
200
220
// NotSupported returns a *Error indicating "unsupported value".
@@ -209,15 +229,15 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
209
229
}
210
230
detail = "supported values: " + strings .Join (quotedValues , ", " )
211
231
}
212
- return & Error {ErrorTypeNotSupported , field .String (), value , detail }
232
+ return & Error {ErrorTypeNotSupported , field .String (), value , detail , "" }
213
233
}
214
234
215
235
// Forbidden returns a *Error indicating "forbidden". This is used to
216
236
// report valid (as per formatting rules) values which would be accepted under
217
237
// some conditions, but which are not permitted by current conditions (e.g.
218
238
// security policy).
219
239
func Forbidden (field * Path , detail string ) * Error {
220
- return & Error {ErrorTypeForbidden , field .String (), "" , detail }
240
+ return & Error {ErrorTypeForbidden , field .String (), "" , detail , "" }
221
241
}
222
242
223
243
// TooLong returns a *Error indicating "too long". This is used to report that
@@ -231,7 +251,7 @@ func TooLong(field *Path, value interface{}, maxLength int) *Error {
231
251
} else {
232
252
msg = "value is too long"
233
253
}
234
- return & Error {ErrorTypeTooLong , field .String (), "<value omitted>" , msg }
254
+ return & Error {ErrorTypeTooLong , field .String (), "<value omitted>" , msg , "" }
235
255
}
236
256
237
257
// TooLongMaxLength returns a *Error indicating "too long".
@@ -259,14 +279,14 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
259
279
actual = omitValue
260
280
}
261
281
262
- return & Error {ErrorTypeTooMany , field .String (), actual , msg }
282
+ return & Error {ErrorTypeTooMany , field .String (), actual , msg , "" }
263
283
}
264
284
265
285
// InternalError returns a *Error indicating "internal error". This is used
266
286
// to signal that an error was found that was not directly related to user
267
287
// input. The err argument must be non-nil.
268
288
func InternalError (field * Path , err error ) * Error {
269
- return & Error {ErrorTypeInternal , field .String (), nil , err .Error ()}
289
+ return & Error {ErrorTypeInternal , field .String (), nil , err .Error (), "" }
270
290
}
271
291
272
292
// ErrorList holds a set of Errors. It is plausible that we might one day have
@@ -285,6 +305,14 @@ func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
285
305
}
286
306
}
287
307
308
+ // WithOrigin sets the origin for all errors in the list and returns the updated list.
309
+ func (list ErrorList ) WithOrigin (origin string ) ErrorList {
310
+ for _ , err := range list {
311
+ err .Origin = origin
312
+ }
313
+ return list
314
+ }
315
+
288
316
// ToAggregate converts the ErrorList into an errors.Aggregate.
289
317
func (list ErrorList ) ToAggregate () utilerrors.Aggregate {
290
318
if len (list ) == 0 {
0 commit comments