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