1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package proto
18
19import (
20	"fmt"
21	"sort"
22	"strings"
23
24	"github.com/googleapis/gnostic/OpenAPIv2"
25	"gopkg.in/yaml.v2"
26)
27
28func 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.
37func 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.
61type Definitions struct {
62	models map[string]Schema
63}
64
65var _ Models = &Definitions{}
66
67// NewOpenAPIData creates a new `Models` out of the openapi document.
68func 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
94func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, error) {
95	if len(s.GetProperties().GetAdditionalProperties()) > 0 {
96		return nil, newSchemaError(path, "unallowed embedded type definition")
97	}
98	if len(s.GetType().GetValue()) > 0 {
99		return nil, newSchemaError(path, "definition reference can't have a type")
100	}
101
102	if !strings.HasPrefix(s.GetXRef(), "#/definitions/") {
103		return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef())
104	}
105	reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/")
106	if _, ok := d.models[reference]; !ok {
107		return nil, newSchemaError(path, "unknown model in reference: %q", reference)
108	}
109	return &Ref{
110		BaseSchema:  d.parseBaseSchema(s, path),
111		reference:   reference,
112		definitions: d,
113	}, nil
114}
115
116func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) BaseSchema {
117	return BaseSchema{
118		Description: s.GetDescription(),
119		Extensions:  VendorExtensionToMap(s.GetVendorExtension()),
120		Path:        *path,
121	}
122}
123
124// We believe the schema is a map, verify and return a new schema
125func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) {
126	if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
127		return nil, newSchemaError(path, "invalid object type")
128	}
129	var sub Schema
130	if s.GetAdditionalProperties().GetSchema() == nil {
131		sub = &Arbitrary{
132			BaseSchema: d.parseBaseSchema(s, path),
133		}
134	} else {
135		var err error
136		sub, err = d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
137		if err != nil {
138			return nil, err
139		}
140	}
141	return &Map{
142		BaseSchema: d.parseBaseSchema(s, path),
143		SubType:    sub,
144	}, nil
145}
146
147func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) {
148	var t string
149	if len(s.GetType().GetValue()) > 1 {
150		return nil, newSchemaError(path, "primitive can't have more than 1 type")
151	}
152	if len(s.GetType().GetValue()) == 1 {
153		t = s.GetType().GetValue()[0]
154	}
155	switch t {
156	case String: // do nothing
157	case Number: // do nothing
158	case Integer: // do nothing
159	case Boolean: // do nothing
160	default:
161		return nil, newSchemaError(path, "Unknown primitive type: %q", t)
162	}
163	return &Primitive{
164		BaseSchema: d.parseBaseSchema(s, path),
165		Type:       t,
166		Format:     s.GetFormat(),
167	}, nil
168}
169
170func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) {
171	if len(s.GetType().GetValue()) != 1 {
172		return nil, newSchemaError(path, "array should have exactly one type")
173	}
174	if s.GetType().GetValue()[0] != array {
175		return nil, newSchemaError(path, `array should have type "array"`)
176	}
177	if len(s.GetItems().GetSchema()) != 1 {
178		return nil, newSchemaError(path, "array should have exactly one sub-item")
179	}
180	sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path)
181	if err != nil {
182		return nil, err
183	}
184	return &Array{
185		BaseSchema: d.parseBaseSchema(s, path),
186		SubType:    sub,
187	}, nil
188}
189
190func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) {
191	if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
192		return nil, newSchemaError(path, "invalid object type")
193	}
194	if s.GetProperties() == nil {
195		return nil, newSchemaError(path, "object doesn't have properties")
196	}
197
198	fields := map[string]Schema{}
199	fieldOrder := []string{}
200
201	for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
202		var err error
203		name := namedSchema.GetName()
204		path := path.FieldPath(name)
205		fields[name], err = d.ParseSchema(namedSchema.GetValue(), &path)
206		if err != nil {
207			return nil, err
208		}
209		fieldOrder = append(fieldOrder, name)
210	}
211
212	return &Kind{
213		BaseSchema:     d.parseBaseSchema(s, path),
214		RequiredFields: s.GetRequired(),
215		Fields:         fields,
216		FieldOrder:     fieldOrder,
217	}, nil
218}
219
220func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
221	return &Arbitrary{
222		BaseSchema: d.parseBaseSchema(s, path),
223	}, nil
224}
225
226// ParseSchema creates a walkable Schema from an openapi schema. While
227// this function is public, it doesn't leak through the interface.
228func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
229	if s.GetXRef() != "" {
230		return d.parseReference(s, path)
231	}
232	objectTypes := s.GetType().GetValue()
233	switch len(objectTypes) {
234	case 0:
235		// in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
236		// the type:object property (they only included the "properties" property), so we need to handle this case
237		if s.GetProperties() != nil {
238			return d.parseKind(s, path)
239		} else {
240			// Definition has no type and no properties. Treat it as an arbitrary value
241			// TODO: what if it has additionalProperties or patternProperties?
242			return d.parseArbitrary(s, path)
243		}
244	case 1:
245		t := objectTypes[0]
246		switch t {
247		case object:
248			if s.GetProperties() != nil {
249				return d.parseKind(s, path)
250			} else {
251				return d.parseMap(s, path)
252			}
253		case array:
254			return d.parseArray(s, path)
255		}
256		return d.parsePrimitive(s, path)
257	default:
258		// the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
259		return nil, newSchemaError(path, "definitions with multiple types aren't supported")
260	}
261}
262
263// LookupModel is public through the interface of Models. It
264// returns a visitable schema from the given model name.
265func (d *Definitions) LookupModel(model string) Schema {
266	return d.models[model]
267}
268
269func (d *Definitions) ListModels() []string {
270	models := []string{}
271
272	for model := range d.models {
273		models = append(models, model)
274	}
275
276	sort.Strings(models)
277	return models
278}
279
280type Ref struct {
281	BaseSchema
282
283	reference   string
284	definitions *Definitions
285}
286
287var _ Reference = &Ref{}
288
289func (r *Ref) Reference() string {
290	return r.reference
291}
292
293func (r *Ref) SubSchema() Schema {
294	return r.definitions.models[r.reference]
295}
296
297func (r *Ref) Accept(v SchemaVisitor) {
298	v.VisitReference(r)
299}
300
301func (r *Ref) GetName() string {
302	return fmt.Sprintf("Reference to %q", r.reference)
303}
304