Skip to content

Commit d9ac9f3

Browse files
authored
✨ Allow maps of arrays of non-string types (#617)
* Allow maps of arrays of non-string types * Correct golden file test * Fix golangci linter error
1 parent 5e6dc15 commit d9ac9f3

File tree

4 files changed

+126
-10
lines changed

4 files changed

+126
-10
lines changed

pkg/crd/schema.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@ const (
3939
defPrefix = "#/definitions/"
4040
)
4141

42-
var (
43-
// byteType is the types.Type for byte (see the types documention
44-
// for why we need to look this up in the Universe), saved
45-
// for quick comparison.
46-
byteType = types.Universe.Lookup("byte").Type()
47-
)
42+
// byteType is the types.Type for byte (see the types documention
43+
// for why we need to look this up in the Universe), saved
44+
// for quick comparison.
45+
var byteType = types.Universe.Lookup("byte").Type()
4846

4947
// SchemaMarker is any marker that needs to modify the schema of the underlying type or field.
5048
type SchemaMarker interface {
@@ -309,10 +307,6 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *apiext.JSONSchemaPro
309307
valSchema = namedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
310308
case *ast.ArrayType:
311309
valSchema = arrayToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
312-
if valSchema.Type == "array" && valSchema.Items.Schema.Type != "string" {
313-
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("not a supported map value type: %T", mapType.Value), mapType.Value))
314-
return &apiext.JSONSchemaProps{}
315-
}
316310
case *ast.StarExpr:
317311
valSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
318312
case *ast.MapType:

pkg/crd/schema_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crd
18+
19+
import (
20+
"go/ast"
21+
"strings"
22+
"testing"
23+
24+
"github.com/onsi/gomega"
25+
"golang.org/x/tools/go/packages"
26+
pkgstest "golang.org/x/tools/go/packages/packagestest"
27+
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28+
testloader "sigs.k8s.io/controller-tools/pkg/loader/testutils"
29+
"sigs.k8s.io/controller-tools/pkg/markers"
30+
)
31+
32+
func transform(t *testing.T, expr string) *apiext.JSONSchemaProps {
33+
// this is *very* hacky but I haven’t found a simple way
34+
// to get an ast.Expr with all the associated metadata required
35+
// to run typeToSchema upon it:
36+
37+
moduleName := "sigs.k8s.io/controller-tools/pkg/crd"
38+
modules := []pkgstest.Module{
39+
{
40+
Name: moduleName,
41+
Files: map[string]interface{}{
42+
"test.go": `
43+
package crd
44+
type Test ` + expr,
45+
},
46+
},
47+
}
48+
49+
pkgs, exported, err := testloader.LoadFakeRoots(pkgstest.Modules, modules, moduleName)
50+
if exported != nil {
51+
t.Cleanup(exported.Cleanup)
52+
}
53+
54+
if err != nil {
55+
t.Fatalf("unable to load fake package: %s", err)
56+
}
57+
58+
if len(pkgs) != 1 {
59+
t.Fatal("expected to parse only one package")
60+
}
61+
62+
pkg := pkgs[0]
63+
pkg.NeedTypesInfo()
64+
failIfErrors(t, pkg.Errors)
65+
66+
schemaContext := newSchemaContext(pkg, nil, true).ForInfo(&markers.TypeInfo{})
67+
// yick: grab the only type definition
68+
definedType := pkg.Syntax[0].Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type
69+
result := typeToSchema(schemaContext, definedType)
70+
failIfErrors(t, pkg.Errors)
71+
return result
72+
}
73+
74+
func failIfErrors(t *testing.T, errs []packages.Error) {
75+
if len(errs) > 0 {
76+
var msgs []string
77+
for _, e := range errs {
78+
msgs = append(msgs, e.Msg)
79+
}
80+
81+
t.Fatalf("error loading fake package: %s", strings.Join(msgs, "; "))
82+
}
83+
}
84+
85+
var arrayOfNumbersSchema *apiext.JSONSchemaProps = &apiext.JSONSchemaProps{
86+
Type: "array",
87+
Items: &apiext.JSONSchemaPropsOrArray{
88+
Schema: &apiext.JSONSchemaProps{
89+
Type: "number",
90+
},
91+
},
92+
}
93+
94+
func Test_Schema_ArrayOfFloat32(t *testing.T) {
95+
g := gomega.NewWithT(t)
96+
97+
output := transform(t, "[]float32")
98+
g.Expect(output).To(gomega.Equal(arrayOfNumbersSchema))
99+
}
100+
101+
func Test_Schema_MapOfStringToArrayOfFloat32(t *testing.T) {
102+
g := gomega.NewWithT(t)
103+
104+
output := transform(t, "map[string][]float32")
105+
g.Expect(output).To(gomega.Equal(&apiext.JSONSchemaProps{
106+
Type: "object",
107+
AdditionalProperties: &apiext.JSONSchemaPropsOrBool{
108+
Allows: true,
109+
Schema: arrayOfNumbersSchema,
110+
},
111+
}))
112+
}

pkg/crd/testdata/cronjob_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ type CronJobSpec struct {
178178

179179
// Checks that maps containing types that contain maps work
180180
ContainsNestedMapMap map[string]ContainsNestedMap `json:"nestedMapInStruct,omitempty"`
181+
182+
// Maps of arrays of things-that-aren’t-strings are permitted
183+
MapOfArraysOfFloats map[string][]bool `json:"mapOfArraysOfFloats,omitempty"`
181184
}
182185

183186
type ContainsNestedMap struct {

pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7176,6 +7176,13 @@ spec:
71767176
- bar
71777177
- foo
71787178
type: object
7179+
mapOfArraysOfFloats:
7180+
additionalProperties:
7181+
items:
7182+
type: boolean
7183+
type: array
7184+
description: Maps of arrays of things-that-aren’t-strings are permitted
7185+
type: object
71797186
mapOfInfo:
71807187
additionalProperties:
71817188
format: byte

0 commit comments

Comments
 (0)