Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2017 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package proto |
| 18 | |
| 19 | import ( |
| 20 | "fmt" |
| 21 | "sort" |
| 22 | "strings" |
| 23 | ) |
| 24 | |
| 25 | // Defines openapi types. |
| 26 | const ( |
| 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. |
| 40 | type 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. |
| 53 | type 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. |
| 64 | type SchemaVisitorArbitrary interface { |
| 65 | SchemaVisitor |
| 66 | VisitArbitrary(*Arbitrary) |
| 67 | } |
| 68 | |
| 69 | // Schema is the base definition of an openapi type. |
| 70 | type 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 |
| 85 | type Path struct { |
| 86 | parent *Path |
| 87 | key string |
| 88 | } |
| 89 | |
| 90 | func NewPath(key string) Path { |
| 91 | return Path{key: key} |
| 92 | } |
| 93 | |
| 94 | func (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 | |
| 104 | func (p *Path) Len() int { |
| 105 | return len(p.Get()) |
| 106 | } |
| 107 | |
| 108 | func (p *Path) String() string { |
| 109 | return strings.Join(p.Get(), "") |
| 110 | } |
| 111 | |
| 112 | // ArrayPath appends an array index and creates a new path |
| 113 | func (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 |
| 121 | func (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. |
| 129 | type BaseSchema struct { |
| 130 | Description string |
| 131 | Extensions map[string]interface{} |
| 132 | |
| 133 | Path Path |
| 134 | } |
| 135 | |
| 136 | func (b *BaseSchema) GetDescription() string { |
| 137 | return b.Description |
| 138 | } |
| 139 | |
| 140 | func (b *BaseSchema) GetExtensions() map[string]interface{} { |
| 141 | return b.Extensions |
| 142 | } |
| 143 | |
| 144 | func (b *BaseSchema) GetPath() *Path { |
| 145 | return &b.Path |
| 146 | } |
| 147 | |
| 148 | // Array must have all its element of the same `SubType`. |
| 149 | type Array struct { |
| 150 | BaseSchema |
| 151 | |
| 152 | SubType Schema |
| 153 | } |
| 154 | |
| 155 | var _ Schema = &Array{} |
| 156 | |
| 157 | func (a *Array) Accept(v SchemaVisitor) { |
| 158 | v.VisitArray(a) |
| 159 | } |
| 160 | |
| 161 | func (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`. |
| 169 | type 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 | |
| 178 | var _ Schema = &Kind{} |
| 179 | |
| 180 | func (k *Kind) Accept(v SchemaVisitor) { |
| 181 | v.VisitKind(k) |
| 182 | } |
| 183 | |
| 184 | func (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. |
| 193 | func (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. |
| 203 | func (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`. |
| 214 | type Map struct { |
| 215 | BaseSchema |
| 216 | |
| 217 | SubType Schema |
| 218 | } |
| 219 | |
| 220 | var _ Schema = &Map{} |
| 221 | |
| 222 | func (m *Map) Accept(v SchemaVisitor) { |
| 223 | v.VisitMap(m) |
| 224 | } |
| 225 | |
| 226 | func (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. |
| 232 | type 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 | |
| 240 | var _ Schema = &Primitive{} |
| 241 | |
| 242 | func (p *Primitive) Accept(v SchemaVisitor) { |
| 243 | v.VisitPrimitive(p) |
| 244 | } |
| 245 | |
| 246 | func (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) |
| 254 | type Arbitrary struct { |
| 255 | BaseSchema |
| 256 | } |
| 257 | |
| 258 | var _ Schema = &Arbitrary{} |
| 259 | |
| 260 | func (a *Arbitrary) Accept(v SchemaVisitor) { |
| 261 | if visitor, ok := v.(SchemaVisitorArbitrary); ok { |
| 262 | visitor.VisitArbitrary(a) |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | func (a *Arbitrary) GetName() string { |
| 267 | return "Arbitrary value (primitive, object or array)" |
| 268 | } |
| 269 | |
| 270 | // Reference implementation depends on the type of document. |
| 271 | type Reference interface { |
| 272 | Schema |
| 273 | |
| 274 | Reference() string |
| 275 | SubSchema() Schema |
| 276 | } |