Skip to content

Commit f5f9484

Browse files
authored
Merge pull request kubernetes#130695 from yongruilin/validation-gen_coveredbydeclarative
[Declarative Validation] Add CoveredByDeclarative to field error struct
2 parents a38d4e5 + 8eb90fe commit f5f9484

File tree

2 files changed

+141
-10
lines changed

2 files changed

+141
-10
lines changed

staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ type Error struct {
4747
// Origin should be set in the most deeply nested validation function that
4848
// can still identify the unique source of the error.
4949
Origin string
50+
51+
// CoveredByDeclarative is true when this error is covered by declarative
52+
// validation. This field is to identify errors from imperative validation
53+
// that should also be caught by declarative validation.
54+
CoveredByDeclarative bool
5055
}
5156

5257
var _ error = &Error{}
@@ -116,6 +121,12 @@ func (e *Error) WithOrigin(o string) *Error {
116121
return e
117122
}
118123

124+
// MarkCoveredByDeclarative marks the error as covered by declarative validation.
125+
func (e *Error) MarkCoveredByDeclarative() *Error {
126+
e.CoveredByDeclarative = true
127+
return e
128+
}
129+
119130
// ErrorType is a machine readable value providing more detail about why
120131
// a field is invalid. These values are expected to match 1-1 with
121132
// CauseType in api/types.go.
@@ -189,32 +200,32 @@ func (t ErrorType) String() string {
189200

190201
// TypeInvalid returns a *Error indicating "type is invalid"
191202
func TypeInvalid(field *Path, value interface{}, detail string) *Error {
192-
return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, ""}
203+
return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false}
193204
}
194205

195206
// NotFound returns a *Error indicating "value not found". This is
196207
// used to report failure to find a requested value (e.g. looking up an ID).
197208
func NotFound(field *Path, value interface{}) *Error {
198-
return &Error{ErrorTypeNotFound, field.String(), value, "", ""}
209+
return &Error{ErrorTypeNotFound, field.String(), value, "", "", false}
199210
}
200211

201212
// Required returns a *Error indicating "value required". This is used
202213
// to report required values that are not provided (e.g. empty strings, null
203214
// values, or empty arrays).
204215
func Required(field *Path, detail string) *Error {
205-
return &Error{ErrorTypeRequired, field.String(), "", detail, ""}
216+
return &Error{ErrorTypeRequired, field.String(), "", detail, "", false}
206217
}
207218

208219
// Duplicate returns a *Error indicating "duplicate value". This is
209220
// used to report collisions of values that must be unique (e.g. names or IDs).
210221
func Duplicate(field *Path, value interface{}) *Error {
211-
return &Error{ErrorTypeDuplicate, field.String(), value, "", ""}
222+
return &Error{ErrorTypeDuplicate, field.String(), value, "", "", false}
212223
}
213224

214225
// Invalid returns a *Error indicating "invalid value". This is used
215226
// to report malformed values (e.g. failed regex match, too long, out of bounds).
216227
func Invalid(field *Path, value interface{}, detail string) *Error {
217-
return &Error{ErrorTypeInvalid, field.String(), value, detail, ""}
228+
return &Error{ErrorTypeInvalid, field.String(), value, detail, "", false}
218229
}
219230

220231
// NotSupported returns a *Error indicating "unsupported value".
@@ -229,15 +240,15 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
229240
}
230241
detail = "supported values: " + strings.Join(quotedValues, ", ")
231242
}
232-
return &Error{ErrorTypeNotSupported, field.String(), value, detail, ""}
243+
return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false}
233244
}
234245

235246
// Forbidden returns a *Error indicating "forbidden". This is used to
236247
// report valid (as per formatting rules) values which would be accepted under
237248
// some conditions, but which are not permitted by current conditions (e.g.
238249
// security policy).
239250
func Forbidden(field *Path, detail string) *Error {
240-
return &Error{ErrorTypeForbidden, field.String(), "", detail, ""}
251+
return &Error{ErrorTypeForbidden, field.String(), "", detail, "", false}
241252
}
242253

243254
// TooLong returns a *Error indicating "too long". This is used to report that
@@ -251,7 +262,7 @@ func TooLong(field *Path, value interface{}, maxLength int) *Error {
251262
} else {
252263
msg = "value is too long"
253264
}
254-
return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, ""}
265+
return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, "", false}
255266
}
256267

257268
// TooLongMaxLength returns a *Error indicating "too long".
@@ -279,14 +290,14 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
279290
actual = omitValue
280291
}
281292

282-
return &Error{ErrorTypeTooMany, field.String(), actual, msg, ""}
293+
return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false}
283294
}
284295

285296
// InternalError returns a *Error indicating "internal error". This is used
286297
// to signal that an error was found that was not directly related to user
287298
// input. The err argument must be non-nil.
288299
func InternalError(field *Path, err error) *Error {
289-
return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), ""}
300+
return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), "", false}
290301
}
291302

292303
// ErrorList holds a set of Errors. It is plausible that we might one day have
@@ -313,6 +324,14 @@ func (list ErrorList) WithOrigin(origin string) ErrorList {
313324
return list
314325
}
315326

327+
// MarkCoveredByDeclarative marks all errors in the list as covered by declarative validation.
328+
func (list ErrorList) MarkCoveredByDeclarative() ErrorList {
329+
for _, err := range list {
330+
err.CoveredByDeclarative = true
331+
}
332+
return list
333+
}
334+
316335
// ToAggregate converts the ErrorList into an errors.Aggregate.
317336
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
318337
if len(list) == 0 {
@@ -349,3 +368,25 @@ func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
349368
// FilterOut takes an Aggregate and returns an Aggregate
350369
return fromAggregate(err.(utilerrors.Aggregate))
351370
}
371+
372+
// ExtractCoveredByDeclarative returns a new ErrorList containing only the errors that should be covered by declarative validation.
373+
func (list ErrorList) ExtractCoveredByDeclarative() ErrorList {
374+
newList := ErrorList{}
375+
for _, err := range list {
376+
if err.CoveredByDeclarative {
377+
newList = append(newList, err)
378+
}
379+
}
380+
return newList
381+
}
382+
383+
// RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation.
384+
func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
385+
newList := ErrorList{}
386+
for _, err := range list {
387+
if !err.CoveredByDeclarative {
388+
newList = append(newList, err)
389+
}
390+
}
391+
return newList
392+
}

staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package field
1818

1919
import (
2020
"fmt"
21+
"reflect"
2122
"strings"
2223
"testing"
2324
)
@@ -211,3 +212,92 @@ func TestErrorListOrigin(t *testing.T) {
211212
}
212213
}
213214
}
215+
216+
func TestErrorMarkDeclarative(t *testing.T) {
217+
// Test for single Error
218+
err := Invalid(NewPath("field"), "value", "detail")
219+
if err.CoveredByDeclarative {
220+
t.Errorf("New error should not be declarative by default")
221+
}
222+
223+
// Mark as declarative
224+
err.MarkCoveredByDeclarative() //nolint:errcheck // The "error" here is not an unexpected error from the function.
225+
if !err.CoveredByDeclarative {
226+
t.Errorf("Error should be declarative after marking")
227+
}
228+
}
229+
230+
func TestErrorListMarkDeclarative(t *testing.T) {
231+
// Test for ErrorList
232+
list := ErrorList{
233+
Invalid(NewPath("field1"), "value1", "detail1"),
234+
Invalid(NewPath("field2"), "value2", "detail2"),
235+
}
236+
237+
// Verify none are declarative by default
238+
for i, err := range list {
239+
if err.CoveredByDeclarative {
240+
t.Errorf("Error %d should not be declarative by default", i)
241+
}
242+
}
243+
244+
// Mark list as declarative
245+
list.MarkCoveredByDeclarative()
246+
247+
// Verify all errors in the list are now declarative
248+
for i, err := range list {
249+
if !err.CoveredByDeclarative {
250+
t.Errorf("Error %d should be declarative after marking the list", i)
251+
}
252+
}
253+
}
254+
255+
func TestErrorListExtractCoveredByDeclarative(t *testing.T) {
256+
testCases := []struct {
257+
list ErrorList
258+
expectedList ErrorList
259+
}{
260+
{
261+
ErrorList{},
262+
ErrorList{},
263+
},
264+
{
265+
ErrorList{Invalid(NewPath("field1"), nil, "")},
266+
ErrorList{},
267+
},
268+
{
269+
ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")},
270+
ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative()},
271+
},
272+
}
273+
274+
for _, tc := range testCases {
275+
got := tc.list.ExtractCoveredByDeclarative()
276+
if !reflect.DeepEqual(got, tc.expectedList) {
277+
t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got)
278+
}
279+
}
280+
}
281+
282+
func TestErrorListRemoveCoveredByDeclarative(t *testing.T) {
283+
testCases := []struct {
284+
list ErrorList
285+
expectedList ErrorList
286+
}{
287+
{
288+
ErrorList{},
289+
ErrorList{},
290+
},
291+
{
292+
ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")},
293+
ErrorList{Required(NewPath("field2"), "detail2")},
294+
},
295+
}
296+
297+
for _, tc := range testCases {
298+
got := tc.list.RemoveCoveredByDeclarative()
299+
if !reflect.DeepEqual(got, tc.expectedList) {
300+
t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got)
301+
}
302+
}
303+
}

0 commit comments

Comments
 (0)