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 compatibility, 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	// Default for that schema.
81	GetDefault() interface{}
82	// Returns type extensions.
83	GetExtensions() map[string]interface{}
84}
85
86// Path helps us keep track of type paths
87type Path struct {
88	parent *Path
89	key    string
90}
91
92func NewPath(key string) Path {
93	return Path{key: key}
94}
95
96func (p *Path) Get() []string {
97	if p == nil {
98		return []string{}
99	}
100	if p.key == "" {
101		return p.parent.Get()
102	}
103	return append(p.parent.Get(), p.key)
104}
105
106func (p *Path) Len() int {
107	return len(p.Get())
108}
109
110func (p *Path) String() string {
111	return strings.Join(p.Get(), "")
112}
113
114// ArrayPath appends an array index and creates a new path
115func (p *Path) ArrayPath(i int) Path {
116	return Path{
117		parent: p,
118		key:    fmt.Sprintf("[%d]", i),
119	}
120}
121
122// FieldPath appends a field name and creates a new path
123func (p *Path) FieldPath(field string) Path {
124	return Path{
125		parent: p,
126		key:    fmt.Sprintf(".%s", field),
127	}
128}
129
130// BaseSchema holds data used by each types of schema.
131type BaseSchema struct {
132	Description string
133	Extensions  map[string]interface{}
134	Default     interface{}
135
136	Path Path
137}
138
139func (b *BaseSchema) GetDescription() string {
140	return b.Description
141}
142
143func (b *BaseSchema) GetExtensions() map[string]interface{} {
144	return b.Extensions
145}
146
147func (b *BaseSchema) GetDefault() interface{} {
148	return b.Default
149}
150
151func (b *BaseSchema) GetPath() *Path {
152	return &b.Path
153}
154
155// Array must have all its element of the same `SubType`.
156type Array struct {
157	BaseSchema
158
159	SubType Schema
160}
161
162var _ Schema = &Array{}
163
164func (a *Array) Accept(v SchemaVisitor) {
165	v.VisitArray(a)
166}
167
168func (a *Array) GetName() string {
169	return fmt.Sprintf("Array of %s", a.SubType.GetName())
170}
171
172// Kind is a complex object. It can have multiple different
173// subtypes for each field, as defined in the `Fields` field. Mandatory
174// fields are listed in `RequiredFields`. The key of the object is
175// always of type `string`.
176type Kind struct {
177	BaseSchema
178
179	// Lists names of required fields.
180	RequiredFields []string
181	// Maps field names to types.
182	Fields map[string]Schema
183	// FieldOrder reports the canonical order for the fields.
184	FieldOrder []string
185}
186
187var _ Schema = &Kind{}
188
189func (k *Kind) Accept(v SchemaVisitor) {
190	v.VisitKind(k)
191}
192
193func (k *Kind) GetName() string {
194	properties := []string{}
195	for key := range k.Fields {
196		properties = append(properties, key)
197	}
198	return fmt.Sprintf("Kind(%v)", properties)
199}
200
201// IsRequired returns true if `field` is a required field for this type.
202func (k *Kind) IsRequired(field string) bool {
203	for _, f := range k.RequiredFields {
204		if f == field {
205			return true
206		}
207	}
208	return false
209}
210
211// Keys returns a alphabetically sorted list of keys.
212func (k *Kind) Keys() []string {
213	keys := make([]string, 0)
214	for key := range k.Fields {
215		keys = append(keys, key)
216	}
217	sort.Strings(keys)
218	return keys
219}
220
221// Map is an object who values must all be of the same `SubType`.
222// The key of the object is always of type `string`.
223type Map struct {
224	BaseSchema
225
226	SubType Schema
227}
228
229var _ Schema = &Map{}
230
231func (m *Map) Accept(v SchemaVisitor) {
232	v.VisitMap(m)
233}
234
235func (m *Map) GetName() string {
236	return fmt.Sprintf("Map of %s", m.SubType.GetName())
237}
238
239// Primitive is a literal. There can be multiple types of primitives,
240// and this subtype can be visited through the `subType` field.
241type Primitive struct {
242	BaseSchema
243
244	// Type of a primitive must be one of: integer, number, string, boolean.
245	Type   string
246	Format string
247}
248
249var _ Schema = &Primitive{}
250
251func (p *Primitive) Accept(v SchemaVisitor) {
252	v.VisitPrimitive(p)
253}
254
255func (p *Primitive) GetName() string {
256	if p.Format == "" {
257		return p.Type
258	}
259	return fmt.Sprintf("%s (%s)", p.Type, p.Format)
260}
261
262// Arbitrary is a value of any type (primitive, object or array)
263type Arbitrary struct {
264	BaseSchema
265}
266
267var _ Schema = &Arbitrary{}
268
269func (a *Arbitrary) Accept(v SchemaVisitor) {
270	if visitor, ok := v.(SchemaVisitorArbitrary); ok {
271		visitor.VisitArbitrary(a)
272	}
273}
274
275func (a *Arbitrary) GetName() string {
276	return "Arbitrary value (primitive, object or array)"
277}
278
279// Reference implementation depends on the type of document.
280type Reference interface {
281	Schema
282
283	Reference() string
284	SubSchema() Schema
285}
286