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
200	for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
201		var err error
202		path := path.FieldPath(namedSchema.GetName())
203		fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path)
204		if err != nil {
205			return nil, err
206		}
207	}
208
209	return &Kind{
210		BaseSchema:     d.parseBaseSchema(s, path),
211		RequiredFields: s.GetRequired(),
212		Fields:         fields,
213	}, nil
214}
215
216func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
217	return &Arbitrary{
218		BaseSchema: d.parseBaseSchema(s, path),
219	}, nil
220}
221
222// ParseSchema creates a walkable Schema from an openapi schema. While
223// this function is public, it doesn't leak through the interface.
224func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
225	if s.GetXRef() != "" {
226		return d.parseReference(s, path)
227	}
228	objectTypes := s.GetType().GetValue()
229	switch len(objectTypes) {
230	case 0:
231		// in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
232		// the type:object property (they only included the "properties" property), so we need to handle this case
233		if s.GetProperties() != nil {
234			return d.parseKind(s, path)
235		} else {
236			// Definition has no type and no properties. Treat it as an arbitrary value
237			// TODO: what if it has additionalProperties or patternProperties?
238			return d.parseArbitrary(s, path)
239		}
240	case 1:
241		t := objectTypes[0]
242		switch t {
243		case object:
244			if s.GetProperties() != nil {
245				return d.parseKind(s, path)
246			} else {
247				return d.parseMap(s, path)
248			}
249		case array:
250			return d.parseArray(s, path)
251		}
252		return d.parsePrimitive(s, path)
253	default:
254		// the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
255		return nil, newSchemaError(path, "definitions with multiple types aren't supported")
256	}
257}
258
259// LookupModel is public through the interface of Models. It
260// returns a visitable schema from the given model name.
261func (d *Definitions) LookupModel(model string) Schema {
262	return d.models[model]
263}
264
265func (d *Definitions) ListModels() []string {
266	models := []string{}
267
268	for model := range d.models {
269		models = append(models, model)
270	}
271
272	sort.Strings(models)
273	return models
274}
275
276type Ref struct {
277	BaseSchema
278
279	reference   string
280	definitions *Definitions
281}
282
283var _ Reference = &Ref{}
284
285func (r *Ref) Reference() string {
286	return r.reference
287}
288
289func (r *Ref) SubSchema() Schema {
290	return r.definitions.models[r.reference]
291}
292
293func (r *Ref) Accept(v SchemaVisitor) {
294	v.VisitReference(r)
295}
296
297func (r *Ref) GetName() string {
298	return fmt.Sprintf("Reference to %q", r.reference)
299}
300