diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go index f5c342a8e82..b1d7114bf3e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go @@ -73,6 +73,15 @@ func ToStructuralOpenAPIV2(in *structuralschema.Structural) *structuralschema.St changed = true } + for f, fs := range s.Properties { + if fs.Nullable { + s.ValueValidation.Required, changed = filterOut(s.ValueValidation.Required, f) + } + } + if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil && s.AdditionalProperties.Structural.Nullable { + s.ValueValidation.Required, changed = nil, true + } + return changed }, // we drop all junctors above, and hence, never reach nested value validations @@ -82,3 +91,24 @@ func ToStructuralOpenAPIV2(in *structuralschema.Structural) *structuralschema.St return out } + +func filterOut(ss []string, x string) ([]string, bool) { + var filtered []string + for i, s := range ss { + if s == x { + if filtered == nil { + filtered = make([]string, i, len(ss)) + copy(filtered, ss[:i]) + } + } else if filtered != nil { + filtered = append(filtered, s) + } + } + if filtered != nil { + if len(filtered) == 0 { + return nil, true + } + return filtered, true + } + return ss, false +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go index 9f98f60185a..8180d33fd26 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go @@ -172,6 +172,63 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) { }, expected: new(spec.Schema), }, + { + name: "nullable required", + in: &apiextensions.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensions.JSONSchemaProps{ + "a": { + Nullable: true, + Type: "string", + }, + "b": { + Nullable: true, + Type: "string", + }, + "c": { + Type: "string", + }, + }, + Required: []string{"a", "c"}, + }, + expected: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "a": {}, + "b": {}, + "c": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + Required: []string{"c"}, + }, + }, + }, + { + name: "nullable required additionalProperties", + in: &apiextensions.JSONSchemaProps{ + Type: "object", + AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ + Schema: &apiextensions.JSONSchemaProps{ + Nullable: true, + Type: "string", + }, + }, + Required: []string{"a", "c"}, + }, + expected: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{}, + }, + }, + }, + }, { name: "title", in: &apiextensions.JSONSchemaProps{ @@ -829,6 +886,34 @@ func fuzzFuncs(f *fuzz.Fuzzer, refFunc func(ref *spec.Ref, c fuzz.Continue, visi ) } +func TestFilterOut(t *testing.T) { + type Test struct { + name string + input []string + x string + expected []string + expectedChanged bool + } + for _, tt := range []Test{ + {"nil", nil, "foo", nil, false}, + {"empty", []string{}, "foo", []string{}, false}, + {"foo", []string{"foo"}, "foo", nil, true}, + {"aaa", []string{"a", "a", "a"}, "a", nil, true}, + {"abc", []string{"a", "b", "c"}, "c", []string{"a", "b"}, true}, + {"abbbcc", []string{"a", "b", "b", "b", "c", "c"}, "b", []string{"a", "c", "c"}, true}, + } { + t.Run(tt.name, func(t *testing.T) { + got, gotChanged := filterOut(tt.input, tt.x) + if !reflect.DeepEqual(tt.expected, got) { + t.Errorf("expected slice %v, got %v", tt.expected, got) + } + if tt.expectedChanged != gotChanged { + t.Errorf("expected changed %v, got %v", tt.expected, got) + } + }) + } +} + func max(i, j int) int { if i > j { return i