unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
error-handlingtesting-strategyapi-design

Fix misleading CRD minItems/minProperties validation errors

Project: kubernetes/kubernetes | Language: Go | Concepts: error-handling, testing-strategy, api-design | Difficulty: mid
Link: https://github.com/kubernetes/kubernetes/pull/141641

The problem

When validating CustomResourceDefinitions (CRDs), the error messages for failing minItems and minProperties constraints were misleading. Instead of clearly indicating that a field had too few items or properties, the error would generically say the value was invalid, showing the count as the offending value. This was inconsistent with other validation errors and confusing for users.

Why it's non-trivial

A naive fix might just change the error message, but the problem was that the validation code did not properly map the relevant OpenAPI error codes to the correct error constructors. Without correctly mapping these codes, the error reporting logic would not use the intended error type (TooFew) and cause type, leading to inconsistent or incorrect error handling elsewhere in the API machinery. Also, proper test coverage was needed to ensure the fix worked for both minItems and minProperties.

The approach

1. Map the correct OpenAPI error codes to the appropriate error constructors

Previously, the kubeOpenAPIResultToFieldErrors function did not handle MinItemsFailCode and TooFewPropertiesCode, so these fell through to a default case. The fix explicitly matches these codes and uses the field.TooFew constructor:

-			case openapierrors.TooManyPropertiesCode:
+			case openapierrors.MinItemsFailCode, openapierrors.TooFewPropertiesCode:
 				actual := int64(-1)
 				if i, ok := err.Value.(int64); ok {
 					actual = i
 				}
-				max := int64(-1)
+				min := int64(-1)
 				if i, ok := err.Valid.(int64); ok {
-					max = i
+					min = i
 				}
-				allErrs = append(allErrs, field.TooMany(errPath, int(actual), int(max)))
+				allErrs = append(allErrs, field.TooFew(errPath, int(actual), int(min)))

What this does:
This snippet ensures that when a minItems or minProperties validation fails, the error is reported as "Too few", using the actual and minimum required values, which is clearer and consistent with other validation errors.

2. Add missing cause types for error reporting

The API machinery uses cause types to classify errors. The PR adds CauseTypeTooFew and CauseTypeTooShort to the list of supported cause types:

+	// CauseTypeTooShort is used to report that the given value is too short.
+	// This is similar to ErrorTypeInvalid. See TooShort().
+	CauseTypeTooShort CauseType = "FieldValueTooShort"
...
+	// CauseTypeTooFew is used to report "too few". This is used to
+	// report that a given list has too few items. This is similar to FieldValueTooLong,
+	// but the error indicates quantity instead of length.
+	CauseTypeTooFew CauseType = "FieldValueTooFew"

What this does:
This ensures that when these new error types are used, they are properly categorized for clients and tooling that consume API error responses.

3. Add test cases to verify the fix

The PR adds targeted test cases for both minProperties and minItems:

+		{name: "minProperties",
+			schema: apiextensions.JSONSchemaProps{
+				Type: "object",
+				Properties: map[string]apiextensions.JSONSchemaProps{
+					"fieldX": {
+						Type:          "object",
+						MinProperties: ptr.To[int64](1),
+					},
+				},
+			},
+			failingObjects: []failingObject{
+				{object: map[string]interface{}{"fieldX": map[string]interface{}{}}, expectErrs: []string{
+					`fieldX: Too few: 0: must have at least 1 item`,
+				}},
+			},
+		},
+		{name: "minItems",
+			schema: apiextensions.JSONSchemaProps{
+				Type: "object",
+				Properties: map[string]apiextensions.JSONSchemaProps{
+					"fieldX": {
+						Type:     "array",
+						MinItems: ptr.To[int64](2),
+					},
+				},
+			},
+			failingObjects: []failingObject{
+				{object: map[string]interface{}{"fieldX": []interface{}{"a"}}, expectErrs: []string{
+					`fieldX: Too few: 1: must have at least 2 items`,
+				}},
+			},
+		},

What this does:
These tests ensure the new error handling works as intended and that the error messages are clear and accurate.

From the review thread

nit: can we put this directly below causeTypeTooLong? Just for code organization..
— jpbetz

Takeaways

  • Explicitly map error codes to error constructors: Always handle all relevant error codes explicitly in validation logic to ensure correct and consistent error reporting.
  • Add and use specific error cause types: Define and use precise error cause types to improve error clarity and enable better downstream error handling.
  • Test for all edge cases: Add targeted tests for each validation rule to verify that error messages and behaviors are correct after changes.
Fix misleading CRD minItems/minProperties validation errors — nossletter.tech