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