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	openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
25	yaml "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	if s.GetAdditionalProperties().GetSchema() == nil {
130		return nil, newSchemaError(path, "invalid object doesn't have additional properties")
131	}
132	sub, err := d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
133	if err != nil {
134		return nil, err
135	}
136	return &Map{
137		BaseSchema: d.parseBaseSchema(s, path),
138		SubType:    sub,
139	}, nil
140}
141
142func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) {
143	var t string
144	if len(s.GetType().GetValue()) > 1 {
145		return nil, newSchemaError(path, "primitive can't have more than 1 type")
146	}
147	if len(s.GetType().GetValue()) == 1 {
148		t = s.GetType().GetValue()[0]
149	}
150	switch t {
151	case String:
152	case Number:
153	case Integer:
154	case Boolean:
155	case "": // Some models are completely empty, and can be safely ignored.
156		// Do nothing
157	default:
158		return nil, newSchemaError(path, "Unknown primitive type: %q", t)
159	}
160	return &Primitive{
161		BaseSchema: d.parseBaseSchema(s, path),
162		Type:       t,
163		Format:     s.GetFormat(),
164	}, nil
165}
166
167func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) {
168	if len(s.GetType().GetValue()) != 1 {
169		return nil, newSchemaError(path, "array should have exactly one type")
170	}
171	if s.GetType().GetValue()[0] != array {
172		return nil, newSchemaError(path, `array should have type "array"`)
173	}
174	if len(s.GetItems().GetSchema()) != 1 {
175		return nil, newSchemaError(path, "array should have exactly one sub-item")
176	}
177	sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path)
178	if err != nil {
179		return nil, err
180	}
181	return &Array{
182		BaseSchema: d.parseBaseSchema(s, path),
183		SubType:    sub,
184	}, nil
185}
186
187func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) {
188	if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
189		return nil, newSchemaError(path, "invalid object type")
190	}
191	if s.GetProperties() == nil {
192		return nil, newSchemaError(path, "object doesn't have properties")
193	}
194
195	fields := map[string]Schema{}
196
197	for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
198		var err error
199		path := path.FieldPath(namedSchema.GetName())
200		fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path)
201		if err != nil {
202			return nil, err
203		}
204	}
205
206	return &Kind{
207		BaseSchema:     d.parseBaseSchema(s, path),
208		RequiredFields: s.GetRequired(),
209		Fields:         fields,
210	}, nil
211}
212
213func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
214	return &Arbitrary{
215		BaseSchema: d.parseBaseSchema(s, path),
216	}, nil
217}
218
219// ParseSchema creates a walkable Schema from an openapi schema. While
220// this function is public, it doesn't leak through the interface.
221func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
222	objectTypes := s.GetType().GetValue()
223	if len(objectTypes) == 1 {
224		t := objectTypes[0]
225		switch t {
226		case object:
227			return d.parseMap(s, path)
228		case array:
229			return d.parseArray(s, path)
230		}
231
232	}
233	if s.GetXRef() != "" {
234		return d.parseReference(s, path)
235	}
236	if s.GetProperties() != nil {
237		return d.parseKind(s, path)
238	}
239	if len(objectTypes) == 0 || (len(objectTypes) == 1 && objectTypes[0] == "") {
240		return d.parseArbitrary(s, path)
241	}
242	return d.parsePrimitive(s, path)
243}
244
245// LookupModel is public through the interface of Models. It
246// returns a visitable schema from the given model name.
247func (d *Definitions) LookupModel(model string) Schema {
248	return d.models[model]
249}
250
251func (d *Definitions) ListModels() []string {
252	models := []string{}
253
254	for model := range d.models {
255		models = append(models, model)
256	}
257
258	sort.Strings(models)
259	return models
260}
261
262type Ref struct {
263	BaseSchema
264
265	reference   string
266	definitions *Definitions
267}
268
269var _ Reference = &Ref{}
270
271func (r *Ref) Reference() string {
272	return r.reference
273}
274
275func (r *Ref) SubSchema() Schema {
276	return r.definitions.models[r.reference]
277}
278
279func (r *Ref) Accept(v SchemaVisitor) {
280	v.VisitReference(r)
281}
282
283func (r *Ref) GetName() string {
284	return fmt.Sprintf("Reference to %q", r.reference)
285}
286