From a38f944cf71ebc8c529ff00acd28a55144f9deac Mon Sep 17 00:00:00 2001 From: "Dr. Stefan Schimanski" Date: Tue, 7 May 2019 21:42:19 +0200 Subject: [PATCH] apiextensions: add defaulting algorithm --- .../apiserver/schema/defaulting/algorithm.go | 55 ++++++ .../schema/defaulting/algorithm_test.go | 165 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm_test.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go new file mode 100644 index 00000000000..2c699898e11 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go @@ -0,0 +1,55 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaulting + +import ( + structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + "k8s.io/apimachinery/pkg/runtime" +) + +// Default does defaulting of x depending on default values in s. +// Default values from s are deep-copied. +func Default(x interface{}, s *structuralschema.Structural) { + if s == nil { + return + } + + switch x := x.(type) { + case map[string]interface{}: + for k, prop := range s.Properties { + if prop.Default.Object == nil { + continue + } + if _, found := x[k]; !found { + x[k] = runtime.DeepCopyJSONValue(prop.Default.Object) + } + } + for k, v := range x { + if prop, found := s.Properties[k]; found { + Default(v, &prop) + } else if s.AdditionalProperties != nil { + Default(v, s.AdditionalProperties.Structural) + } + } + case []interface{}: + for _, v := range x { + Default(v, s.Items) + } + default: + // scalars, do nothing + } +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm_test.go new file mode 100644 index 00000000000..ceef3c91399 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm_test.go @@ -0,0 +1,165 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaulting + +import ( + "bytes" + "reflect" + "testing" + + structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + "k8s.io/apimachinery/pkg/util/json" +) + +func TestDefault(t *testing.T) { + tests := []struct { + name string + json string + schema *structuralschema.Structural + expected string + }{ + {"empty", "null", nil, "null"}, + {"scalar", "4", &structuralschema.Structural{ + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"foo"}, + }, + }, "4"}, + {"scalar array", "[1,2]", &structuralschema.Structural{ + Items: &structuralschema.Structural{ + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"foo"}, + }, + }, + }, "[1,2]"}, + {"object array", `[{"a":1},{"b":1},{"c":1}]`, &structuralschema.Structural{ + Items: &structuralschema.Structural{ + Properties: map[string]structuralschema.Structural{ + "a": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"A"}, + }, + }, + "b": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"B"}, + }, + }, + "c": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"C"}, + }, + }, + }, + }, + }, `[{"a":1,"b":"B","c":"C"},{"a":"A","b":1,"c":"C"},{"a":"A","b":"B","c":1}]`}, + {"object array object", `{"array":[{"a":1},{"b":2}],"object":{"a":1},"additionalProperties":{"x":{"a":1},"y":{"b":2}}}`, &structuralschema.Structural{ + Properties: map[string]structuralschema.Structural{ + "array": { + Items: &structuralschema.Structural{ + Properties: map[string]structuralschema.Structural{ + "a": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"A"}, + }, + }, + "b": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"B"}, + }, + }, + }, + }, + }, + "object": { + Properties: map[string]structuralschema.Structural{ + "a": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"N"}, + }, + }, + "b": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"O"}, + }, + }, + }, + }, + "additionalProperties": { + Generic: structuralschema.Generic{ + AdditionalProperties: &structuralschema.StructuralOrBool{ + Structural: &structuralschema.Structural{ + Properties: map[string]structuralschema.Structural{ + "a": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"alpha"}, + }, + }, + "b": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"beta"}, + }, + }, + }, + }, + }, + }, + }, + "foo": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"bar"}, + }, + }, + }, + }, `{"array":[{"a":1,"b":"B"},{"a":"A","b":2}],"object":{"a":1,"b":"O"},"additionalProperties":{"x":{"a":1,"b":"beta"},"y":{"a":"alpha","b":2}},"foo":"bar"}`}, + {"empty and null", `[{},{"a":1},{"a":0},{"a":0.0},{"a":""},{"a":null},{"a":[]},{"a":{}}]`, &structuralschema.Structural{ + Items: &structuralschema.Structural{ + Properties: map[string]structuralschema.Structural{ + "a": { + Generic: structuralschema.Generic{ + Default: structuralschema.JSON{"A"}, + }, + }, + }, + }, + }, `[{"a":"A"},{"a":1},{"a":0},{"a":0.0},{"a":""},{"a":null},{"a":[]},{"a":{}}]`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var in interface{} + if err := json.Unmarshal([]byte(tt.json), &in); err != nil { + t.Fatal(err) + } + + var expected interface{} + if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil { + t.Fatal(err) + } + + Default(in, tt.schema) + if !reflect.DeepEqual(in, expected) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetIndent("", " ") + err := enc.Encode(in) + if err != nil { + t.Fatalf("unexpected result mashalling error: %v", err) + } + t.Errorf("expected: %s\ngot: %s", tt.expected, buf.String()) + } + }) + } +}