diff --git a/pkg/apiserver/schema/cel/compilation.go b/pkg/apiserver/schema/cel/compilation.go index 4f065a18f..07ba73105 100644 --- a/pkg/apiserver/schema/cel/compilation.go +++ b/pkg/apiserver/schema/cel/compilation.go @@ -17,6 +17,7 @@ limitations under the License. package cel import ( + "errors" "fmt" "strings" "time" @@ -125,6 +126,9 @@ func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit if len(s.Extensions.XValidations) == 0 { return nil, nil } + if declType == nil { + return nil, errors.New("failed to convert to declType for CEL validation rules") + } celRules := s.Extensions.XValidations oldSelfEnvSet, optionalOldSelfEnvSet, err := prepareEnvSet(baseEnvSet, declType) diff --git a/pkg/apiserver/schema/cel/model/adaptor.go b/pkg/apiserver/schema/cel/model/adaptor.go index 0bc109a73..aeed2a858 100644 --- a/pkg/apiserver/schema/cel/model/adaptor.go +++ b/pkg/apiserver/schema/cel/model/adaptor.go @@ -62,6 +62,9 @@ func (s *Structural) Pattern() string { } func (s *Structural) Items() common.Schema { + if s.Structural.Items == nil { + return nil + } return &Structural{Structural: s.Structural.Items} } diff --git a/pkg/apiserver/schema/cel/model/schemas_test.go b/pkg/apiserver/schema/cel/model/schemas_test.go index 04ae3a1a4..f43f3e908 100644 --- a/pkg/apiserver/schema/cel/model/schemas_test.go +++ b/pkg/apiserver/schema/cel/model/schemas_test.go @@ -274,6 +274,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { Name string InputSchema *schema.Structural ExpectedMaxElements int64 + ExpectNilType bool } tests := []maxLengthTest{ { @@ -499,13 +500,61 @@ func TestEstimateMaxLengthJSON(t *testing.T) { // so we expect the max length to be exactly equal to the user-supplied one ExpectedMaxElements: 20, }, + { + Name: "Property under array", + InputSchema: &schema.Structural{ + Generic: schema.Generic{ + Type: "array", + }, + Properties: map[string]schema.Structural{ + "field": { + Generic: schema.Generic{ + Type: "string", + Default: schema.JSON{Object: "default"}, + }, + }, + }, + }, + // Got nil for delType + ExpectedMaxElements: 0, + ExpectNilType: true, + }, + { + Name: "Items under object", + InputSchema: &schema.Structural{ + Generic: schema.Generic{ + Type: "object", + }, + Items: &schema.Structural{ + Generic: schema.Generic{ + Type: "array", + }, + Properties: map[string]schema.Structural{ + "field": { + Generic: schema.Generic{ + Type: "string", + Default: schema.JSON{Object: "default"}, + }, + }, + }, + ValueValidation: &schema.ValueValidation{ + Required: []string{"field"}, + }, + }, + }, + // Skip items under object for schema conversion. + ExpectedMaxElements: 0, + }, } for _, testCase := range tests { t.Run(testCase.Name, func(t *testing.T) { decl := SchemaDeclType(testCase.InputSchema, false) - if decl.MaxElements != testCase.ExpectedMaxElements { + if decl != nil && decl.MaxElements != testCase.ExpectedMaxElements { t.Errorf("wrong maxElements (got %d, expected %d)", decl.MaxElements, testCase.ExpectedMaxElements) } + if testCase.ExpectNilType && decl != nil { + t.Errorf("expected nil type, got %v", decl) + } }) } } diff --git a/pkg/apiserver/schema/cel/validation.go b/pkg/apiserver/schema/cel/validation.go index d9b595805..835f13eb5 100644 --- a/pkg/apiserver/schema/cel/validation.go +++ b/pkg/apiserver/schema/cel/validation.go @@ -94,8 +94,14 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType compiledRules, err := Compile(s, declType, perCallLimit, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true), StoredExpressionsEnvLoader()) var itemsValidator, additionalPropertiesValidator *Validator var propertiesValidators map[string]Validator + var elemType *cel.DeclType + if declType != nil { + elemType = declType.ElemType + } else { + elemType = declType + } if s.Items != nil { - itemsValidator = validator(s.Items, s.Items.XEmbeddedResource, declType.ElemType, perCallLimit) + itemsValidator = validator(s.Items, s.Items.XEmbeddedResource, elemType, perCallLimit) } if len(s.Properties) > 0 { propertiesValidators = make(map[string]Validator, len(s.Properties)) @@ -103,6 +109,9 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType prop := p var fieldType *cel.DeclType if escapedPropName, ok := cel.Escape(k); ok { + if declType == nil { + continue + } if f, ok := declType.Fields[escapedPropName]; ok { fieldType = f.Type } else { @@ -123,7 +132,7 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType } } if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil { - additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource, declType.ElemType, perCallLimit) + additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource, elemType, perCallLimit) } if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 { activationFactory := validationActivationWithoutOldSelf