Update err handling

This commit is contained in:
Jordan Liggitt 2022-03-24 10:47:24 -07:00 committed by cici37
parent 460121fa1e
commit 50795b1afa
3 changed files with 109 additions and 12 deletions

View File

@ -84,14 +84,25 @@ func ValidateCustomResource(fldPath *field.Path, customResource interface{}, val
if err.Value != nil {
value = err.Value
}
allErrs = append(allErrs, field.TooLongFail(errPath, value, err.Error()))
case openapierrors.TooManyPropertiesCode, openapierrors.MaxItemsFailCode:
value := interface{}("")
if err.Value != nil {
value = err.Value
max := int64(-1)
if i, ok := err.Value.(int64); ok {
max = i
}
allErrs = append(allErrs, field.TooManyFail(errPath, value, err.Error()))
allErrs = append(allErrs, field.TooLongMaxLength(errPath, value, int(max)))
case openapierrors.MaxItemsFailCode:
max := int64(-1)
if i, ok := err.Value.(int64); ok {
max = i
}
allErrs = append(allErrs, field.TooMany(errPath, -1, int(max)))
case openapierrors.TooManyPropertiesCode:
max := int64(-1)
if i, ok := err.Value.(int64); ok {
max = i
}
allErrs = append(allErrs, field.TooMany(errPath, -1, int(max)))
case openapierrors.InvalidTypeCode:
value := interface{}("")

View File

@ -26,10 +26,9 @@ import (
"github.com/google/go-cmp/cmp"
utilpointer "k8s.io/utils/pointer"
kjson "sigs.k8s.io/json"
kubeopenapispec "k8s.io/kube-openapi/pkg/validation/spec"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@ -41,6 +40,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
kubeopenapispec "k8s.io/kube-openapi/pkg/validation/spec"
)
// TestRoundTrip checks the conversion to go-openapi types.
@ -535,6 +535,51 @@ func TestValidateCustomResource(t *testing.T) {
}},
},
},
{name: "maxProperties",
schema: apiextensions.JSONSchemaProps{
Properties: map[string]apiextensions.JSONSchemaProps{
"fieldX": {
Type: "object",
MaxProperties: utilpointer.Int64(2),
},
},
},
failingObjects: []failingObject{
{object: map[string]interface{}{"fieldX": map[string]interface{}{"a": true, "b": true, "c": true}}, expectErrs: []string{
`fieldX: Too many: must have at most 2 items`,
}},
},
},
{name: "maxItems",
schema: apiextensions.JSONSchemaProps{
Properties: map[string]apiextensions.JSONSchemaProps{
"fieldX": {
Type: "array",
MaxItems: utilpointer.Int64(2),
},
},
},
failingObjects: []failingObject{
{object: map[string]interface{}{"fieldX": []interface{}{"a", "b", "c"}}, expectErrs: []string{
`fieldX: Too many: must have at most 3 items`,
}},
},
},
{name: "maxLength",
schema: apiextensions.JSONSchemaProps{
Properties: map[string]apiextensions.JSONSchemaProps{
"fieldX": {
Type: "string",
MaxLength: utilpointer.Int64(2),
},
},
},
failingObjects: []failingObject{
{object: map[string]interface{}{"fieldX": "abc"}, expectErrs: []string{
`fieldX: Too long: value is too long`,
}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -42,12 +42,24 @@ func (v *Error) Error() string {
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
}
type omitValueType struct{}
var omitValue = omitValueType{}
// ErrorBody returns the error message without the field name. This is useful
// for building nice-looking higher-level error reporting.
func (v *Error) ErrorBody() string {
var s string
switch v.Type {
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
switch {
case v.Type == ErrorTypeRequired:
s = v.Type.String()
case v.Type == ErrorTypeForbidden:
s = v.Type.String()
case v.Type == ErrorTypeTooLong:
s = v.Type.String()
case v.Type == ErrorTypeInternal:
s = v.Type.String()
case v.BadValue == omitValue:
s = v.Type.String()
default:
value := v.BadValue
@ -226,11 +238,40 @@ func TooLong(field *Path, value interface{}, maxLength int) *Error {
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d bytes", maxLength)}
}
// TooLongMaxLength returns a *Error indicating "too long". This is used to
// report that the given value is too long. This is similar to
// Invalid, but the returned error will not include the too-long
// value. If maxLength is negative, no max length will be included in the message.
func TooLongMaxLength(field *Path, value interface{}, maxLength int) *Error {
var msg string
if maxLength >= 0 {
msg = fmt.Sprintf("may not be longer than %d", maxLength)
} else {
msg = "value is too long"
}
return &Error{ErrorTypeTooLong, field.String(), value, msg}
}
// TooMany returns a *Error indicating "too many". This is used to
// report that a given list has too many items. This is similar to TooLong,
// but the returned error indicates quantity instead of length.
func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
return &Error{ErrorTypeTooMany, field.String(), actualQuantity, fmt.Sprintf("must have at most %d items", maxQuantity)}
var msg string
if maxQuantity >= 0 {
msg = fmt.Sprintf("must have at most %d items", maxQuantity)
} else {
msg = "has too many items"
}
var actual interface{}
if actualQuantity >= 0 {
actual = actualQuantity
} else {
actual = omitValue
}
return &Error{ErrorTypeTooMany, field.String(), actual, msg}
}
// InternalError returns a *Error indicating "internal error". This is used