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
25// Defines openapi types.
26const (
27	Integer = "integer"
28	Number  = "number"
29	String  = "string"
30	Boolean = "boolean"
31
32	// These types are private as they should never leak, and are
33	// represented by actual structs.
34	array  = "array"
35	object = "object"
36)
37
38// Models interface describe a model provider. They can give you the
39// schema for a specific model.
40type Models interface {
41	LookupModel(string) Schema
42	ListModels() []string
43}
44
45// SchemaVisitor is an interface that you need to implement if you want
46// to "visit" an openapi schema. A dispatch on the Schema type will call
47// the appropriate function based on its actual type:
48// - Array is a list of one and only one given subtype
49// - Map is a map of string to one and only one given subtype
50// - Primitive can be string, integer, number and boolean.
51// - Kind is an object with specific fields mapping to specific types.
52// - Reference is a link to another definition.
53type SchemaVisitor interface {
54	VisitArray(*Array)
55	VisitMap(*Map)
56	VisitPrimitive(*Primitive)
57	VisitKind(*Kind)
58	VisitReference(Reference)
59}
60
61// SchemaVisitorArbitrary is an additional visitor interface which handles
62// arbitrary types. For backwards compatability, it's a separate interface
63// which is checked for at runtime.
64type SchemaVisitorArbitrary interface {
65	SchemaVisitor
66	VisitArbitrary(*Arbitrary)
67}
68
69// Schema is the base definition of an openapi type.
70type Schema interface {
71	// Giving a visitor here will let you visit the actual type.
72	Accept(SchemaVisitor)
73
74	// Pretty print the name of the type.
75	GetName() string
76	// Describes how to access this field.
77	GetPath() *Path
78	// Describes the field.
79	GetDescription() string
80	// Returns type extensions.
81	GetExtensions() map[string]interface{}
82}
83
84// Path helps us keep track of type paths
85type Path struct {
86	parent *Path
87	key    string
88}
89
90func NewPath(key string) Path {
91	return Path{key: key}
92}
93
94func (p *Path) Get() []string {
95	if p == nil {
96		return []string{}
97	}
98	if p.key == "" {
99		return p.parent.Get()
100	}
101	return append(p.parent.Get(), p.key)
102}
103
104func (p *Path) Len() int {
105	return len(p.Get())
106}
107
108func (p *Path) String() string {
109	return strings.Join(p.Get(), "")
110}
111
112// ArrayPath appends an array index and creates a new path
113func (p *Path) ArrayPath(i int) Path {
114	return Path{
115		parent: p,
116		key:    fmt.Sprintf("[%d]", i),
117	}
118}
119
120// FieldPath appends a field name and creates a new path
121func (p *Path) FieldPath(field string) Path {
122	return Path{
123		parent: p,
124		key:    fmt.Sprintf(".%s", field),
125	}
126}
127
128// BaseSchema holds data used by each types of schema.
129type BaseSchema struct {
130	Description string
131	Extensions  map[string]interface{}
132
133	Path Path
134}
135
136func (b *BaseSchema) GetDescription() string {
137	return b.Description
138}
139
140func (b *BaseSchema) GetExtensions() map[string]interface{} {
141	return b.Extensions
142}
143
144func (b *BaseSchema) GetPath() *Path {
145	return &b.Path
146}
147
148// Array must have all its element of the same `SubType`.
149type Array struct {
150	BaseSchema
151
152	SubType Schema
153}
154
155var _ Schema = &Array{}
156
157func (a *Array) Accept(v SchemaVisitor) {
158	v.VisitArray(a)
159}
160
161func (a *Array) GetName() string {
162	return fmt.Sprintf("Array of %s", a.SubType.GetName())
163}
164
165// Kind is a complex object. It can have multiple different
166// subtypes for each field, as defined in the `Fields` field. Mandatory
167// fields are listed in `RequiredFields`. The key of the object is
168// always of type `string`.
169type Kind struct {
170	BaseSchema
171
172	// Lists names of required fields.
173	RequiredFields []string
174	// Maps field names to types.
175	Fields map[string]Schema
176}
177
178var _ Schema = &Kind{}
179
180func (k *Kind) Accept(v SchemaVisitor) {
181	v.VisitKind(k)
182}
183
184func (k *Kind) GetName() string {
185	properties := []string{}
186	for key := range k.Fields {
187		properties = append(properties, key)
188	}
189	return fmt.Sprintf("Kind(%v)", properties)
190}
191
192// IsRequired returns true if `field` is a required field for this type.
193func (k *Kind) IsRequired(field string) bool {
194	for _, f := range k.RequiredFields {
195		if f == field {
196			return true
197		}
198	}
199	return false
200}
201
202// Keys returns a alphabetically sorted list of keys.
203func (k *Kind) Keys() []string {
204	keys := make([]string, 0)
205	for key := range k.Fields {
206		keys = append(keys, key)
207	}
208	sort.Strings(keys)
209	return keys
210}
211
212// Map is an object who values must all be of the same `SubType`.
213// The key of the object is always of type `string`.
214type Map struct {
215	BaseSchema
216
217	SubType Schema
218}
219
220var _ Schema = &Map{}
221
222func (m *Map) Accept(v SchemaVisitor) {
223	v.VisitMap(m)
224}
225
226func (m *Map) GetName() string {
227	return fmt.Sprintf("Map of %s", m.SubType.GetName())
228}
229
230// Primitive is a literal. There can be multiple types of primitives,
231// and this subtype can be visited through the `subType` field.
232type Primitive struct {
233	BaseSchema
234
235	// Type of a primitive must be one of: integer, number, string, boolean.
236	Type   string
237	Format string
238}
239
240var _ Schema = &Primitive{}
241
242func (p *Primitive) Accept(v SchemaVisitor) {
243	v.VisitPrimitive(p)
244}
245
246func (p *Primitive) GetName() string {
247	if p.Format == "" {
248		return p.Type
249	}
250	return fmt.Sprintf("%s (%s)", p.Type, p.Format)
251}
252
253// Arbitrary is a value of any type (primitive, object or array)
254type Arbitrary struct {
255	BaseSchema
256}
257
258var _ Schema = &Arbitrary{}
259
260func (a *Arbitrary) Accept(v SchemaVisitor) {
261	if visitor, ok := v.(SchemaVisitorArbitrary); ok {
262		visitor.VisitArbitrary(a)
263	}
264}
265
266func (a *Arbitrary) GetName() string {
267	return "Arbitrary value (primitive, object or array)"
268}
269
270// Reference implementation depends on the type of document.
271type Reference interface {
272	Schema
273
274	Reference() string
275	SubSchema() Schema
276}
277