@@ -68,15 +68,14 @@ func parseResource(resource interface{}, schema *spec.Schema, path string) ([]va
6868}
6969
7070func getExpectedTypes (schema * spec.Schema ) ([]string , error ) {
71+ // Handle "x-kubernetes-preserve-unknown-fields" extension first
72+ if hasStructuralSchemaMarkerEnabled (schema , xKubernetesPreserveUnknownFields ) {
73+ return []string {schemaTypeAny }, nil
74+ }
75+
7176 // Handle "x-kubernetes-int-or-string" extension
72- if ext , ok := schema .VendorExtensible .Extensions [xKubernetesIntOrString ]; ok {
73- enabled , ok := ext .(bool )
74- if ! ok {
75- return nil , fmt .Errorf ("xKubernetesIntOrString extension is not a boolean" )
76- }
77- if enabled {
78- return []string {"string" , "integer" }, nil
79- }
77+ if hasStructuralSchemaMarkerEnabled (schema , xKubernetesIntOrString ) {
78+ return []string {"string" , "integer" }, nil
8079 }
8180
8281 // Handle OneOf schemas
@@ -110,9 +109,10 @@ func getExpectedTypes(schema *spec.Schema) ([]string, error) {
110109 return types , nil
111110 }
112111
113- if schema .Type [0 ] != "" {
112+ if len ( schema . Type ) > 0 && schema .Type [0 ] != "" {
114113 return schema .Type , nil
115114 }
115+
116116 if schema .AdditionalProperties != nil && schema .AdditionalProperties .Allows {
117117 // NOTE(a-hilaly): I don't like the type "any", we might want to change this to "object"
118118 // in the future; just haven't really thought about it yet.
@@ -123,14 +123,15 @@ func getExpectedTypes(schema *spec.Schema) ([]string, error) {
123123 return nil , fmt .Errorf ("unknown schema type" )
124124}
125125
126- func sliceInclude (expectedTypes []string , expectedType string ) bool {
127- return slices .Contains (expectedTypes , expectedType )
128- }
129-
130126func validateSchema (schema * spec.Schema , path string ) error {
131127 if schema == nil {
132128 return fmt .Errorf ("schema is nil for path %s" , path )
133129 }
130+
131+ if hasStructuralSchemaMarkerEnabled (schema , xKubernetesPreserveUnknownFields ) {
132+ return nil
133+ }
134+
134135 // Ensure the schema has at least one valid construct
135136 if len (schema .Type ) == 0 && len (schema .OneOf ) == 0 && len (schema .AnyOf ) == 0 && schema .AdditionalProperties == nil {
136137 return fmt .Errorf ("schema at path %s has no valid type, OneOf, AnyOf, or AdditionalProperties" , path )
@@ -139,18 +140,14 @@ func validateSchema(schema *spec.Schema, path string) error {
139140}
140141
141142func parseObject (field map [string ]interface {}, schema * spec.Schema , path string , expectedTypes []string ) ([]variable.FieldDescriptor , error ) {
142- if ! sliceInclude (expectedTypes , "object" ) && (schema .AdditionalProperties == nil || ! schema .AdditionalProperties .Allows ) {
143- return nil , fmt .Errorf ("expected object type or AdditionalProperties allowed for path %s, got %v" , path , field )
144- }
145-
146143 // Look for vendor schema extensions first
147144 if len (schema .VendorExtensible .Extensions ) > 0 {
148145 // If the schema has the x-kubernetes-preserve-unknown-fields extension, we need to parse
149146 // this field using the schemaless parser. This allows us to extract CEL expressions from
150147 // fields that don't have a strict schema definition, while still preserving any unknown
151148 // fields. This is particularly important for handling custom resources and fields that
152149 // may contain arbitrary nested structures with potential CEL expressions.
153- if enabled , ok := schema . VendorExtensible . Extensions [ xKubernetesPreserveUnknownFields ]; ok && enabled .( bool ) {
150+ if hasStructuralSchemaMarkerEnabled ( schema , xKubernetesPreserveUnknownFields ) {
154151 expressions , err := parseSchemalessResource (field , path )
155152 if err != nil {
156153 return nil , err
@@ -159,6 +156,10 @@ func parseObject(field map[string]interface{}, schema *spec.Schema, path string,
159156 }
160157 }
161158
159+ if ! slices .Contains (expectedTypes , "object" ) && (schema .AdditionalProperties == nil || ! schema .AdditionalProperties .Allows ) {
160+ return nil , fmt .Errorf ("expected object type or AdditionalProperties allowed for path %s, got %v" , path , field )
161+ }
162+
162163 var expressionsFields []variable.FieldDescriptor
163164 for fieldName , value := range field {
164165 fieldSchema , err := getFieldSchema (schema , fieldName )
@@ -176,7 +177,7 @@ func parseObject(field map[string]interface{}, schema *spec.Schema, path string,
176177}
177178
178179func parseArray (field []interface {}, schema * spec.Schema , path string , expectedTypes []string ) ([]variable.FieldDescriptor , error ) {
179- if ! sliceInclude (expectedTypes , "array" ) {
180+ if ! slices . Contains (expectedTypes , "array" ) {
180181 return nil , fmt .Errorf ("expected array type for path %s, got %v" , path , field )
181182 }
182183
@@ -213,7 +214,7 @@ func parseString(field string, schema *spec.Schema, path string, expectedTypes [
213214 }}, nil
214215 }
215216
216- if ! sliceInclude (expectedTypes , "string" ) && ! sliceInclude (expectedTypes , schemaTypeAny ) {
217+ if ! slices . Contains (expectedTypes , "string" ) && ! slices . Contains (expectedTypes , schemaTypeAny ) {
217218 return nil , fmt .Errorf ("expected string type or AdditionalProperties for path %s, got %v" , path , field )
218219 }
219220
@@ -234,15 +235,15 @@ func parseString(field string, schema *spec.Schema, path string, expectedTypes [
234235func parseScalarTypes (field interface {}, _ * spec.Schema , path string , expectedTypes []string ) ([]variable.FieldDescriptor , error ) {
235236 // perform type checks for scalar types
236237 switch {
237- case sliceInclude (expectedTypes , "number" ):
238+ case slices . Contains (expectedTypes , "number" ):
238239 if _ , ok := field .(float64 ); ! ok {
239240 return nil , fmt .Errorf ("expected number type for path %s, got %T" , path , field )
240241 }
241- case sliceInclude (expectedTypes , "int" ), sliceInclude (expectedTypes , "integer" ):
242+ case slices . Contains (expectedTypes , "int" ), slices . Contains (expectedTypes , "integer" ):
242243 if ! isInteger (field ) {
243244 return nil , fmt .Errorf ("expected integer type for path %s, got %T" , path , field )
244245 }
245- case sliceInclude (expectedTypes , "boolean" ), sliceInclude (expectedTypes , "bool" ):
246+ case slices . Contains (expectedTypes , "boolean" ), slices . Contains (expectedTypes , "bool" ):
246247 if _ , ok := field .(bool ); ! ok {
247248 return nil , fmt .Errorf ("expected boolean type for path %s, got %T" , path , field )
248249 }
@@ -306,3 +307,13 @@ func joinPathAndFieldName(path, fieldName string) string {
306307 }
307308 return fmt .Sprintf ("%s.%s" , path , fieldName )
308309}
310+
311+ // hasStructuralSchemaMarkerEnabled checks if a schema has a specific marker enabled.
312+ func hasStructuralSchemaMarkerEnabled (schema * spec.Schema , marker string ) bool {
313+ if ext , ok := schema .VendorExtensible .Extensions [marker ]; ok {
314+ if enabled , ok := ext .(bool ); ok && enabled {
315+ return true
316+ }
317+ }
318+ return false
319+ }
0 commit comments