mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #130695 from yongruilin/validation-gen_coveredbydeclarative
[Declarative Validation] Add CoveredByDeclarative to field error struct
This commit is contained in:
		| @@ -47,6 +47,11 @@ type Error struct { | ||||
| 	// Origin should be set in the most deeply nested validation function that | ||||
| 	// can still identify the unique source of the error. | ||||
| 	Origin string | ||||
|  | ||||
| 	// CoveredByDeclarative is true when this error is covered by declarative | ||||
| 	// validation. This field is to identify errors from imperative validation | ||||
| 	// that should also be caught by declarative validation. | ||||
| 	CoveredByDeclarative bool | ||||
| } | ||||
|  | ||||
| var _ error = &Error{} | ||||
| @@ -116,6 +121,12 @@ func (e *Error) WithOrigin(o string) *Error { | ||||
| 	return e | ||||
| } | ||||
|  | ||||
| // MarkCoveredByDeclarative marks the error as covered by declarative validation. | ||||
| func (e *Error) MarkCoveredByDeclarative() *Error { | ||||
| 	e.CoveredByDeclarative = true | ||||
| 	return e | ||||
| } | ||||
|  | ||||
| // ErrorType is a machine readable value providing more detail about why | ||||
| // a field is invalid.  These values are expected to match 1-1 with | ||||
| // CauseType in api/types.go. | ||||
| @@ -189,32 +200,32 @@ func (t ErrorType) String() string { | ||||
|  | ||||
| // TypeInvalid returns a *Error indicating "type is invalid" | ||||
| func TypeInvalid(field *Path, value interface{}, detail string) *Error { | ||||
| 	return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, ""} | ||||
| 	return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false} | ||||
| } | ||||
|  | ||||
| // NotFound returns a *Error indicating "value not found".  This is | ||||
| // used to report failure to find a requested value (e.g. looking up an ID). | ||||
| func NotFound(field *Path, value interface{}) *Error { | ||||
| 	return &Error{ErrorTypeNotFound, field.String(), value, "", ""} | ||||
| 	return &Error{ErrorTypeNotFound, field.String(), value, "", "", false} | ||||
| } | ||||
|  | ||||
| // Required returns a *Error indicating "value required".  This is used | ||||
| // to report required values that are not provided (e.g. empty strings, null | ||||
| // values, or empty arrays). | ||||
| func Required(field *Path, detail string) *Error { | ||||
| 	return &Error{ErrorTypeRequired, field.String(), "", detail, ""} | ||||
| 	return &Error{ErrorTypeRequired, field.String(), "", detail, "", false} | ||||
| } | ||||
|  | ||||
| // Duplicate returns a *Error indicating "duplicate value".  This is | ||||
| // used to report collisions of values that must be unique (e.g. names or IDs). | ||||
| func Duplicate(field *Path, value interface{}) *Error { | ||||
| 	return &Error{ErrorTypeDuplicate, field.String(), value, "", ""} | ||||
| 	return &Error{ErrorTypeDuplicate, field.String(), value, "", "", false} | ||||
| } | ||||
|  | ||||
| // Invalid returns a *Error indicating "invalid value".  This is used | ||||
| // to report malformed values (e.g. failed regex match, too long, out of bounds). | ||||
| func Invalid(field *Path, value interface{}, detail string) *Error { | ||||
| 	return &Error{ErrorTypeInvalid, field.String(), value, detail, ""} | ||||
| 	return &Error{ErrorTypeInvalid, field.String(), value, detail, "", false} | ||||
| } | ||||
|  | ||||
| // NotSupported returns a *Error indicating "unsupported value". | ||||
| @@ -229,7 +240,7 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E | ||||
| 		} | ||||
| 		detail = "supported values: " + strings.Join(quotedValues, ", ") | ||||
| 	} | ||||
| 	return &Error{ErrorTypeNotSupported, field.String(), value, detail, ""} | ||||
| 	return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false} | ||||
| } | ||||
|  | ||||
| // Forbidden returns a *Error indicating "forbidden".  This is used to | ||||
| @@ -237,7 +248,7 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E | ||||
| // some conditions, but which are not permitted by current conditions (e.g. | ||||
| // security policy). | ||||
| func Forbidden(field *Path, detail string) *Error { | ||||
| 	return &Error{ErrorTypeForbidden, field.String(), "", detail, ""} | ||||
| 	return &Error{ErrorTypeForbidden, field.String(), "", detail, "", false} | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	} else { | ||||
| 		msg = "value is too long" | ||||
| 	} | ||||
| 	return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, ""} | ||||
| 	return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, "", false} | ||||
| } | ||||
|  | ||||
| // TooLongMaxLength returns a *Error indicating "too long". | ||||
| @@ -279,14 +290,14 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error { | ||||
| 		actual = omitValue | ||||
| 	} | ||||
|  | ||||
| 	return &Error{ErrorTypeTooMany, field.String(), actual, msg, ""} | ||||
| 	return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false} | ||||
| } | ||||
|  | ||||
| // InternalError returns a *Error indicating "internal error".  This is used | ||||
| // to signal that an error was found that was not directly related to user | ||||
| // input.  The err argument must be non-nil. | ||||
| func InternalError(field *Path, err error) *Error { | ||||
| 	return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), ""} | ||||
| 	return &Error{ErrorTypeInternal, field.String(), nil, err.Error(), "", false} | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // MarkCoveredByDeclarative marks all errors in the list as covered by declarative validation. | ||||
| func (list ErrorList) MarkCoveredByDeclarative() ErrorList { | ||||
| 	for _, err := range list { | ||||
| 		err.CoveredByDeclarative = true | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // ToAggregate converts the ErrorList into an errors.Aggregate. | ||||
| func (list ErrorList) ToAggregate() utilerrors.Aggregate { | ||||
| 	if len(list) == 0 { | ||||
| @@ -349,3 +368,25 @@ func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList { | ||||
| 	// FilterOut takes an Aggregate and returns an Aggregate | ||||
| 	return fromAggregate(err.(utilerrors.Aggregate)) | ||||
| } | ||||
|  | ||||
| // ExtractCoveredByDeclarative returns a new ErrorList containing only the errors that should be covered by declarative validation. | ||||
| func (list ErrorList) ExtractCoveredByDeclarative() ErrorList { | ||||
| 	newList := ErrorList{} | ||||
| 	for _, err := range list { | ||||
| 		if err.CoveredByDeclarative { | ||||
| 			newList = append(newList, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return newList | ||||
| } | ||||
|  | ||||
| // RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation. | ||||
| func (list ErrorList) RemoveCoveredByDeclarative() ErrorList { | ||||
| 	newList := ErrorList{} | ||||
| 	for _, err := range list { | ||||
| 		if !err.CoveredByDeclarative { | ||||
| 			newList = append(newList, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return newList | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ package field | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| @@ -211,3 +212,92 @@ func TestErrorListOrigin(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestErrorMarkDeclarative(t *testing.T) { | ||||
| 	// Test for single Error | ||||
| 	err := Invalid(NewPath("field"), "value", "detail") | ||||
| 	if err.CoveredByDeclarative { | ||||
| 		t.Errorf("New error should not be declarative by default") | ||||
| 	} | ||||
|  | ||||
| 	// Mark as declarative | ||||
| 	err.MarkCoveredByDeclarative() //nolint:errcheck // The "error" here is not an unexpected error from the function. | ||||
| 	if !err.CoveredByDeclarative { | ||||
| 		t.Errorf("Error should be declarative after marking") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestErrorListMarkDeclarative(t *testing.T) { | ||||
| 	// Test for ErrorList | ||||
| 	list := ErrorList{ | ||||
| 		Invalid(NewPath("field1"), "value1", "detail1"), | ||||
| 		Invalid(NewPath("field2"), "value2", "detail2"), | ||||
| 	} | ||||
|  | ||||
| 	// Verify none are declarative by default | ||||
| 	for i, err := range list { | ||||
| 		if err.CoveredByDeclarative { | ||||
| 			t.Errorf("Error %d should not be declarative by default", i) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Mark list as declarative | ||||
| 	list.MarkCoveredByDeclarative() | ||||
|  | ||||
| 	// Verify all errors in the list are now declarative | ||||
| 	for i, err := range list { | ||||
| 		if !err.CoveredByDeclarative { | ||||
| 			t.Errorf("Error %d should be declarative after marking the list", i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestErrorListExtractCoveredByDeclarative(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		list         ErrorList | ||||
| 		expectedList ErrorList | ||||
| 	}{ | ||||
| 		{ | ||||
| 			ErrorList{}, | ||||
| 			ErrorList{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ErrorList{Invalid(NewPath("field1"), nil, "")}, | ||||
| 			ErrorList{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")}, | ||||
| 			ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative()}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range testCases { | ||||
| 		got := tc.list.ExtractCoveredByDeclarative() | ||||
| 		if !reflect.DeepEqual(got, tc.expectedList) { | ||||
| 			t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestErrorListRemoveCoveredByDeclarative(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		list         ErrorList | ||||
| 		expectedList ErrorList | ||||
| 	}{ | ||||
| 		{ | ||||
| 			ErrorList{}, | ||||
| 			ErrorList{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")}, | ||||
| 			ErrorList{Required(NewPath("field2"), "detail2")}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range testCases { | ||||
| 		got := tc.list.RemoveCoveredByDeclarative() | ||||
| 		if !reflect.DeepEqual(got, tc.expectedList) { | ||||
| 			t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot