1
16
17 package schemaconv
18
19 import (
20 "fmt"
21 "sort"
22
23 "sigs.k8s.io/structured-merge-diff/v4/schema"
24 )
25
26 const (
27 quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
28 rawExtensionResource = "io.k8s.apimachinery.pkg.runtime.RawExtension"
29 )
30
31 type convert struct {
32 preserveUnknownFields bool
33 output *schema.Schema
34
35 currentName string
36 current *schema.Atom
37 errorMessages []string
38 }
39
40 func (c *convert) push(name string, a *schema.Atom) *convert {
41 return &convert{
42 preserveUnknownFields: c.preserveUnknownFields,
43 output: c.output,
44 currentName: name,
45 current: a,
46 }
47 }
48
49 func (c *convert) top() *schema.Atom { return c.current }
50
51 func (c *convert) pop(c2 *convert) {
52 c.errorMessages = append(c.errorMessages, c2.errorMessages...)
53 }
54
55 func (c *convert) reportError(format string, args ...interface{}) {
56 c.errorMessages = append(c.errorMessages,
57 c.currentName+": "+fmt.Sprintf(format, args...),
58 )
59 }
60
61 func (c *convert) insertTypeDef(name string, atom schema.Atom) {
62 def := schema.TypeDef{
63 Name: name,
64 Atom: atom,
65 }
66 if def.Atom == (schema.Atom{}) {
67
68 return
69 }
70 c.output.Types = append(c.output.Types, def)
71 }
72
73 func (c *convert) addCommonTypes() {
74 c.output.Types = append(c.output.Types, untypedDef)
75 c.output.Types = append(c.output.Types, deducedDef)
76 }
77
78 var untypedName string = "__untyped_atomic_"
79
80 var untypedDef schema.TypeDef = schema.TypeDef{
81 Name: untypedName,
82 Atom: schema.Atom{
83 Scalar: ptr(schema.Scalar("untyped")),
84 List: &schema.List{
85 ElementType: schema.TypeRef{
86 NamedType: &untypedName,
87 },
88 ElementRelationship: schema.Atomic,
89 },
90 Map: &schema.Map{
91 ElementType: schema.TypeRef{
92 NamedType: &untypedName,
93 },
94 ElementRelationship: schema.Atomic,
95 },
96 },
97 }
98
99 var deducedName string = "__untyped_deduced_"
100
101 var deducedDef schema.TypeDef = schema.TypeDef{
102 Name: deducedName,
103 Atom: schema.Atom{
104 Scalar: ptr(schema.Scalar("untyped")),
105 List: &schema.List{
106 ElementType: schema.TypeRef{
107 NamedType: &untypedName,
108 },
109 ElementRelationship: schema.Atomic,
110 },
111 Map: &schema.Map{
112 ElementType: schema.TypeRef{
113 NamedType: &deducedName,
114 },
115 ElementRelationship: schema.Separable,
116 },
117 },
118 }
119
120 func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
121 schemaUnions := []schema.Union{}
122 if iunions, ok := extensions["x-kubernetes-unions"]; ok {
123 unions, ok := iunions.([]interface{})
124 if !ok {
125 return nil, fmt.Errorf(`"x-kubernetes-unions" should be a list, got %#v`, unions)
126 }
127 for _, iunion := range unions {
128 union, ok := iunion.(map[interface{}]interface{})
129 if !ok {
130 return nil, fmt.Errorf(`"x-kubernetes-unions" items should be a map of string to unions, got %#v`, iunion)
131 }
132 unionMap := map[string]interface{}{}
133 for k, v := range union {
134 key, ok := k.(string)
135 if !ok {
136 return nil, fmt.Errorf(`"x-kubernetes-unions" has non-string key: %#v`, k)
137 }
138 unionMap[key] = v
139 }
140 schemaUnion, err := makeUnion(unionMap)
141 if err != nil {
142 return nil, err
143 }
144 schemaUnions = append(schemaUnions, schemaUnion)
145 }
146 }
147
148
149 fs := map[string]struct{}{}
150 for _, u := range schemaUnions {
151 if u.Discriminator != nil {
152 if _, ok := fs[*u.Discriminator]; ok {
153 return nil, fmt.Errorf("%v field appears multiple times in unions", *u.Discriminator)
154 }
155 fs[*u.Discriminator] = struct{}{}
156 }
157 for _, f := range u.Fields {
158 if _, ok := fs[f.FieldName]; ok {
159 return nil, fmt.Errorf("%v field appears multiple times in unions", f.FieldName)
160 }
161 fs[f.FieldName] = struct{}{}
162 }
163 }
164
165 return schemaUnions, nil
166 }
167
168 func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
169 union := schema.Union{
170 Fields: []schema.UnionField{},
171 }
172
173 if idiscriminator, ok := extensions["discriminator"]; ok {
174 discriminator, ok := idiscriminator.(string)
175 if !ok {
176 return schema.Union{}, fmt.Errorf(`"discriminator" must be a string, got: %#v`, idiscriminator)
177 }
178 union.Discriminator = &discriminator
179 }
180
181 if ifields, ok := extensions["fields-to-discriminateBy"]; ok {
182 fields, ok := ifields.(map[interface{}]interface{})
183 if !ok {
184 return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy" must be a map[string]string, got: %#v`, ifields)
185 }
186
187 keys := []string{}
188 for ifield := range fields {
189 field, ok := ifield.(string)
190 if !ok {
191 return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy": field must be a string, got: %#v`, ifield)
192 }
193 keys = append(keys, field)
194
195 }
196 sort.Strings(keys)
197 reverseMap := map[string]struct{}{}
198 for _, field := range keys {
199 value := fields[field]
200 discriminated, ok := value.(string)
201 if !ok {
202 return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy"/%v: value must be a string, got: %#v`, field, value)
203 }
204 union.Fields = append(union.Fields, schema.UnionField{
205 FieldName: field,
206 DiscriminatorValue: discriminated,
207 })
208
209
210 if _, ok := reverseMap[discriminated]; ok {
211 return schema.Union{}, fmt.Errorf("Multiple fields have the same discriminated name: %v", discriminated)
212 }
213 reverseMap[discriminated] = struct{}{}
214 }
215 }
216
217 return union, nil
218 }
219
220 func toStringSlice(o interface{}) (out []string, ok bool) {
221 switch t := o.(type) {
222 case []interface{}:
223 for _, v := range t {
224 switch vt := v.(type) {
225 case string:
226 out = append(out, vt)
227 }
228 }
229 return out, true
230 case []string:
231 return t, true
232 }
233 return nil, false
234 }
235
236 func ptr(s schema.Scalar) *schema.Scalar { return &s }
237
238
239
240 func convertPrimitive(typ string, format string) (a schema.Atom) {
241 switch typ {
242 case "integer":
243 a.Scalar = ptr(schema.Numeric)
244 case "number":
245 a.Scalar = ptr(schema.Numeric)
246 case "string":
247 switch format {
248 case "":
249 a.Scalar = ptr(schema.String)
250 case "byte":
251
252 a.Scalar = ptr(schema.String)
253 case "int-or-string":
254 a.Scalar = ptr(schema.Scalar("untyped"))
255 case "date-time":
256 a.Scalar = ptr(schema.Scalar("untyped"))
257 default:
258 a.Scalar = ptr(schema.Scalar("untyped"))
259 }
260 case "boolean":
261 a.Scalar = ptr(schema.Boolean)
262 default:
263 a.Scalar = ptr(schema.Scalar("untyped"))
264 }
265
266 return a
267 }
268
269 func getListElementRelationship(ext map[string]any) (schema.ElementRelationship, []string, error) {
270 if val, ok := ext["x-kubernetes-list-type"]; ok {
271 switch val {
272 case "atomic":
273 return schema.Atomic, nil, nil
274 case "set":
275 return schema.Associative, nil, nil
276 case "map":
277 keys, ok := ext["x-kubernetes-list-map-keys"]
278
279 if !ok {
280 return schema.Associative, nil, fmt.Errorf("missing map keys")
281 }
282
283 keyNames, ok := toStringSlice(keys)
284 if !ok {
285 return schema.Associative, nil, fmt.Errorf("uninterpreted map keys: %#v", keys)
286 }
287
288 return schema.Associative, keyNames, nil
289 default:
290 return schema.Atomic, nil, fmt.Errorf("unknown list type %v", val)
291 }
292 } else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
293 switch val {
294 case "merge", "merge,retainKeys":
295 if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
296 keyName, ok := key.(string)
297
298 if !ok {
299 return schema.Associative, nil, fmt.Errorf("uninterpreted merge key: %#v", key)
300 }
301
302 return schema.Associative, []string{keyName}, nil
303 }
304
305
306 return schema.Associative, nil, nil
307 case "retainKeys":
308 return schema.Atomic, nil, nil
309 default:
310 return schema.Atomic, nil, fmt.Errorf("unknown patch strategy %v", val)
311 }
312 }
313
314
315 return schema.Atomic, nil, nil
316 }
317
318
319 func getMapElementRelationship(ext map[string]any) (schema.ElementRelationship, error) {
320 val, ok := ext["x-kubernetes-map-type"]
321 if !ok {
322
323 return "", nil
324 }
325
326 switch val {
327 case "atomic":
328 return schema.Atomic, nil
329 case "granular":
330 return schema.Separable, nil
331 default:
332 return "", fmt.Errorf("unknown map type %v", val)
333 }
334 }
335
View as plain text