Change:
The server-side validation does not check the type of values with . Create a CRD like: Then create a CR: It is not rejected, but should because must not be a boolean.

Discussion:
if len(obj.Type) == 0 { obj.Nullable = false // because this does not roundtrip through go-openapi } <ins> if obj.XIntOrString { obj.Type = "" } </ins> }, func(obj *apiextensions.JSONSchemaPropsOrBool, c fuzz.Continue) { if c.RandBool() {

out.Description = in.Description if in.Type != "" { out.Type = spec.StringOrArray([]string{in.Type}) <del> if in.Nullable { out.Type = append(out.Type, "null") } </del> <ins> } if in.XIntOrString { out.VendorExtensible.AddExtension("x-kubernetes-int-or-string", true) out.Type = spec.StringOrArray{"integer", "string"} } if out.Type != nil && in.Nullable { out.Type = append(out.Type, "null") </ins> } out.Format = in.Format out.Title = in.Title

if in.XEmbeddedResource { out.VendorExtensible.AddExtension("x-kubernetes-embedded-resource", true) } <del> if in.XIntOrString { out.VendorExtensible.AddExtension("x-kubernetes-int-or-string", true) } </del> return nil }

t.Fatal(err) } j = convertNullTypeToNullable(j) <ins> j = stripIntOrStringType(j) </ins> openAPIJSON, err = json.Marshal(j) if err != nil { t.Fatal(err)

} } <del> func TestValidateCustomResource(t *testing.T) { type args struct { schema apiextensions.JSONSchemaProps object interface{} </del> <ins> func stripIntOrStringType(x interface{}) interface{} { switch x := x.(type) { case map[string]interface{}: if t, found := x["type"]; found { switch t := t.(type) { case []interface{}: if len(t) == 2 && t[0] == "integer" && t[1] == "string" && x["x-kubernetes-int-or-string"] == true { delete(x, "type") } } } for k := range x { x[k] = stripIntOrStringType(x[k]) } return x case []interface{}: for i := range x { x[i] = stripIntOrStringType(x[i]) } return x default: return x </ins> } <ins> } func TestValidateCustomResource(t *testing.T) { </ins> tests := []struct { <del> name    string args    args wantErr bool </del> <ins> name           string schema         apiextensions.JSONSchemaProps objects        []interface{} failingObjects []interface{} </ins> }{ <del> // TODO: make more complete {"!nullable against non-null", args{ apiextensions.JSONSchemaProps{ </del> <ins> {name: "!nullable", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { Type:     "object",

}, }, }, <del> map[string]interface{}{"field": map[string]interface{}{}}, }, false}, {"!nullable against null", args{ apiextensions.JSONSchemaProps{ Properties: map[string]apiextensions.JSONSchemaProps{ "field": { Type:     "object", Nullable: false, }, }, </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": map[string]interface{}{}}, </ins> }, <del> map[string]interface{}{"field": nil}, }, true}, {"!nullable against undefined", args{ apiextensions.JSONSchemaProps{ </del> <ins> failingObjects: []interface{}{ map[string]interface{}{"field": "foo"}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "nullable", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { Type:     "object", <del> Nullable: false, </del> <ins> Nullable: true, </ins> }, }, }, <del> map[string]interface{}{}, }, false}, {"nullable against non-null", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": nil}, }, failingObjects: []interface{}{ map[string]interface{}{"field": "foo"}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "nullable and no type", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { <del> Type:     "object", </del> Nullable: true, }, }, }, <del> map[string]interface{}{"field": map[string]interface{}{}}, }, false}, {"nullable against null", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": nil}, map[string]interface{}{"field": "foo"}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "x-kubernetes-int-or-string", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { <del> Type:     "object", Nullable: true, </del> <ins> XIntOrString: true, </ins> }, }, }, <del> map[string]interface{}{"field": nil}, }, false}, {"!nullable against undefined", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": "foo"}, }, failingObjects: []interface{}{ map[string]interface{}{"field": nil}, map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "nullable and x-kubernetes-int-or-string", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { <del> Type:     "object", Nullable: true, </del> <ins> Nullable:     true, XIntOrString: true, </ins> }, }, }, <del> map[string]interface{}{}, }, false}, {"nullable and no type against non-nil", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": "foo"}, map[string]interface{}{"field": nil}, }, failingObjects: []interface{}{ map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "nullable, x-kubernetes-int-or-string and user-provided anyOf", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { <del> Nullable: true, </del> <ins> Nullable:     true, XIntOrString: true, AnyOf: []apiextensions.JSONSchemaProps{ {Type: "integer"}, {Type: "string"}, }, </ins> }, }, }, <del> map[string]interface{}{"field": 42}, }, false}, {"nullable and no type against nil", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": nil}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": "foo"}, }, failingObjects: []interface{}{ map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "nullable, x-kubernetes-int-or-string and user-provider allOf", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { <del> Nullable: true, </del> <ins> Nullable:     true, XIntOrString: true, AllOf: []apiextensions.JSONSchemaProps{ { AnyOf: []apiextensions.JSONSchemaProps{ {Type: "integer"}, {Type: "string"}, }, }, }, </ins> }, }, }, <del> map[string]interface{}{"field": nil}, }, false}, {"invalid regex", args{ apiextensions.JSONSchemaProps{ </del> <ins> objects: []interface{}{ map[string]interface{}{}, map[string]interface{}{"field": nil}, map[string]interface{}{"field": 42}, map[string]interface{}{"field": "foo"}, }, failingObjects: []interface{}{ map[string]interface{}{"field": true}, map[string]interface{}{"field": 1.2}, map[string]interface{}{"field": map[string]interface{}{}}, map[string]interface{}{"field": []interface{}{}}, }, }, {name: "invalid regex", schema: apiextensions.JSONSchemaProps{ </ins> Properties: map[string]apiextensions.JSONSchemaProps{ "field": { Type:    "string",

}, }, }, <del> map[string]interface{}{"field": "foo"}, }, true}, </del> <ins> failingObjects: []interface{}{map[string]interface{}{"field": "foo"}}, }, </ins> } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { <del> validator, _, err := NewSchemaValidator(&apiextensions.CustomResourceValidation{OpenAPIV3Schema: &tt.args.schema}) </del> <ins> validator, _, err := NewSchemaValidator(&apiextensions.CustomResourceValidation{OpenAPIV3Schema: &tt.schema}) </ins> if err != nil { t.Fatal(err) } <del> if err := ValidateCustomResource(tt.args.object, validator); (err != nil) != tt.wantErr { if err == nil { t.Error("expected error, but didn't get one") } else { t.Errorf("unexpected validation error: %v", err) </del> <ins> for _, obj := range tt.objects { if err := ValidateCustomResource(obj, validator); err != nil { t.Errorf("unexpected validation error for %v: %v", obj, err) } } for _, obj := range tt.failingObjects { if err := ValidateCustomResource(obj, validator); err == nil { t.Errorf("missing error for %v", obj) </ins> } } })