Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / k8s.io / kube-openapi / pkg / util / proto / document.go
1 /*
2 Copyright 2017 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 proto
18
19 import (
20         "fmt"
21         "sort"
22         "strings"
23
24         "github.com/googleapis/gnostic/OpenAPIv2"
25         "gopkg.in/yaml.v2"
26 )
27
28 func newSchemaError(path *Path, format string, a ...interface{}) error {
29         err := fmt.Sprintf(format, a...)
30         if path.Len() == 0 {
31                 return fmt.Errorf("SchemaError: %v", err)
32         }
33         return fmt.Errorf("SchemaError(%v): %v", path, err)
34 }
35
36 // VendorExtensionToMap converts openapi VendorExtension to a map.
37 func VendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} {
38         values := map[string]interface{}{}
39
40         for _, na := range e {
41                 if na.GetName() == "" || na.GetValue() == nil {
42                         continue
43                 }
44                 if na.GetValue().GetYaml() == "" {
45                         continue
46                 }
47                 var value interface{}
48                 err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value)
49                 if err != nil {
50                         continue
51                 }
52
53                 values[na.GetName()] = value
54         }
55
56         return values
57 }
58
59 // Definitions is an implementation of `Models`. It looks for
60 // models in an openapi Schema.
61 type Definitions struct {
62         models map[string]Schema
63 }
64
65 var _ Models = &Definitions{}
66
67 // NewOpenAPIData creates a new `Models` out of the openapi document.
68 func NewOpenAPIData(doc *openapi_v2.Document) (Models, error) {
69         definitions := Definitions{
70                 models: map[string]Schema{},
71         }
72
73         // Save the list of all models first. This will allow us to
74         // validate that we don't have any dangling reference.
75         for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
76                 definitions.models[namedSchema.GetName()] = nil
77         }
78
79         // Now, parse each model. We can validate that references exists.
80         for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
81                 path := NewPath(namedSchema.GetName())
82                 schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path)
83                 if err != nil {
84                         return nil, err
85                 }
86                 definitions.models[namedSchema.GetName()] = schema
87         }
88
89         return &definitions, nil
90 }
91
92 // We believe the schema is a reference, verify that and returns a new
93 // Schema
94 func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, error) {
95         // TODO(wrong): a schema with a $ref can have properties. We can ignore them (would be incomplete), but we cannot return an error.
96         if len(s.GetProperties().GetAdditionalProperties()) > 0 {
97                 return nil, newSchemaError(path, "unallowed embedded type definition")
98         }
99         // TODO(wrong): a schema with a $ref can have a type. We can ignore it (would be incomplete), but we cannot return an error.
100         if len(s.GetType().GetValue()) > 0 {
101                 return nil, newSchemaError(path, "definition reference can't have a type")
102         }
103
104         // TODO(wrong): $refs outside of the definitions are completely valid. We can ignore them (would be incomplete), but we cannot return an error.
105         if !strings.HasPrefix(s.GetXRef(), "#/definitions/") {
106                 return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef())
107         }
108         reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/")
109         if _, ok := d.models[reference]; !ok {
110                 return nil, newSchemaError(path, "unknown model in reference: %q", reference)
111         }
112         return &Ref{
113                 BaseSchema:  d.parseBaseSchema(s, path),
114                 reference:   reference,
115                 definitions: d,
116         }, nil
117 }
118
119 func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) BaseSchema {
120         return BaseSchema{
121                 Description: s.GetDescription(),
122                 Extensions:  VendorExtensionToMap(s.GetVendorExtension()),
123                 Path:        *path,
124         }
125 }
126
127 // We believe the schema is a map, verify and return a new schema
128 func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) {
129         if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
130                 return nil, newSchemaError(path, "invalid object type")
131         }
132         var sub Schema
133         // TODO(incomplete): this misses the boolean case as AdditionalProperties is a bool+schema sum type.
134         if s.GetAdditionalProperties().GetSchema() == nil {
135                 sub = &Arbitrary{
136                         BaseSchema: d.parseBaseSchema(s, path),
137                 }
138         } else {
139                 var err error
140                 sub, err = d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
141                 if err != nil {
142                         return nil, err
143                 }
144         }
145         return &Map{
146                 BaseSchema: d.parseBaseSchema(s, path),
147                 SubType:    sub,
148         }, nil
149 }
150
151 func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) {
152         var t string
153         if len(s.GetType().GetValue()) > 1 {
154                 return nil, newSchemaError(path, "primitive can't have more than 1 type")
155         }
156         if len(s.GetType().GetValue()) == 1 {
157                 t = s.GetType().GetValue()[0]
158         }
159         switch t {
160         case String: // do nothing
161         case Number: // do nothing
162         case Integer: // do nothing
163         case Boolean: // do nothing
164         // TODO(wrong): this misses "null". Would skip the null case (would be incomplete), but we cannot return an error.
165         default:
166                 return nil, newSchemaError(path, "Unknown primitive type: %q", t)
167         }
168         return &Primitive{
169                 BaseSchema: d.parseBaseSchema(s, path),
170                 Type:       t,
171                 Format:     s.GetFormat(),
172         }, nil
173 }
174
175 func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) {
176         if len(s.GetType().GetValue()) != 1 {
177                 return nil, newSchemaError(path, "array should have exactly one type")
178         }
179         if s.GetType().GetValue()[0] != array {
180                 return nil, newSchemaError(path, `array should have type "array"`)
181         }
182         if len(s.GetItems().GetSchema()) != 1 {
183                 // TODO(wrong): Items can have multiple elements. We can ignore Items then (would be incomplete), but we cannot return an error.
184                 // TODO(wrong): "type: array" witohut any items at all is completely valid.
185                 return nil, newSchemaError(path, "array should have exactly one sub-item")
186         }
187         sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path)
188         if err != nil {
189                 return nil, err
190         }
191         return &Array{
192                 BaseSchema: d.parseBaseSchema(s, path),
193                 SubType:    sub,
194         }, nil
195 }
196
197 func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) {
198         if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
199                 return nil, newSchemaError(path, "invalid object type")
200         }
201         if s.GetProperties() == nil {
202                 return nil, newSchemaError(path, "object doesn't have properties")
203         }
204
205         fields := map[string]Schema{}
206         fieldOrder := []string{}
207
208         for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
209                 var err error
210                 name := namedSchema.GetName()
211                 path := path.FieldPath(name)
212                 fields[name], err = d.ParseSchema(namedSchema.GetValue(), &path)
213                 if err != nil {
214                         return nil, err
215                 }
216                 fieldOrder = append(fieldOrder, name)
217         }
218
219         return &Kind{
220                 BaseSchema:     d.parseBaseSchema(s, path),
221                 RequiredFields: s.GetRequired(),
222                 Fields:         fields,
223                 FieldOrder:     fieldOrder,
224         }, nil
225 }
226
227 func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
228         return &Arbitrary{
229                 BaseSchema: d.parseBaseSchema(s, path),
230         }, nil
231 }
232
233 // ParseSchema creates a walkable Schema from an openapi schema. While
234 // this function is public, it doesn't leak through the interface.
235 func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
236         if s.GetXRef() != "" {
237                 // TODO(incomplete): ignoring the rest of s is wrong. As long as there are no conflict, everything from s must be considered
238                 // Reference: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object
239                 return d.parseReference(s, path)
240         }
241         objectTypes := s.GetType().GetValue()
242         switch len(objectTypes) {
243         case 0:
244                 // in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
245                 // the type:object property (they only included the "properties" property), so we need to handle this case
246                 // TODO: validate that we ever published empty, non-nil properties. JSON roundtripping nils them.
247                 if s.GetProperties() != nil {
248                         // TODO(wrong): when verifying a non-object later against this, it will be rejected as invalid type.
249                         // TODO(CRD validation schema publishing): we have to filter properties (empty or not) if type=object is not given
250                         return d.parseKind(s, path)
251                 } else {
252                         // Definition has no type and no properties. Treat it as an arbitrary value
253                         // TODO(incomplete): what if it has additionalProperties=false or patternProperties?
254                         // ANSWER: parseArbitrary is less strict than it has to be with patternProperties (which is ignored). So this is correct (of course not complete).
255                         return d.parseArbitrary(s, path)
256                 }
257         case 1:
258                 t := objectTypes[0]
259                 switch t {
260                 case object:
261                         if s.GetProperties() != nil {
262                                 return d.parseKind(s, path)
263                         } else {
264                                 return d.parseMap(s, path)
265                         }
266                 case array:
267                         return d.parseArray(s, path)
268                 }
269                 return d.parsePrimitive(s, path)
270         default:
271                 // the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
272                 // TODO(wrong): this is rejecting a completely valid OpenAPI spec
273                 // TODO(CRD validation schema publishing): filter these out
274                 return nil, newSchemaError(path, "definitions with multiple types aren't supported")
275         }
276 }
277
278 // LookupModel is public through the interface of Models. It
279 // returns a visitable schema from the given model name.
280 func (d *Definitions) LookupModel(model string) Schema {
281         return d.models[model]
282 }
283
284 func (d *Definitions) ListModels() []string {
285         models := []string{}
286
287         for model := range d.models {
288                 models = append(models, model)
289         }
290
291         sort.Strings(models)
292         return models
293 }
294
295 type Ref struct {
296         BaseSchema
297
298         reference   string
299         definitions *Definitions
300 }
301
302 var _ Reference = &Ref{}
303
304 func (r *Ref) Reference() string {
305         return r.reference
306 }
307
308 func (r *Ref) SubSchema() Schema {
309         return r.definitions.models[r.reference]
310 }
311
312 func (r *Ref) Accept(v SchemaVisitor) {
313         v.VisitReference(r)
314 }
315
316 func (r *Ref) GetName() string {
317         return fmt.Sprintf("Reference to %q", r.reference)
318 }