Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame] | 1 | // Copyright 2015 go-swagger maintainers |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package spec |
| 16 | |
| 17 | import ( |
| 18 | "encoding/json" |
| 19 | "fmt" |
| 20 | "log" |
| 21 | "net/url" |
| 22 | "os" |
| 23 | "path" |
| 24 | "path/filepath" |
| 25 | "reflect" |
| 26 | "strings" |
| 27 | "sync" |
| 28 | |
| 29 | "github.com/go-openapi/jsonpointer" |
| 30 | "github.com/go-openapi/swag" |
| 31 | ) |
| 32 | |
| 33 | var ( |
| 34 | // Debug enables logging when SWAGGER_DEBUG env var is not empty |
| 35 | Debug = os.Getenv("SWAGGER_DEBUG") != "" |
| 36 | ) |
| 37 | |
| 38 | // ExpandOptions provides options for expand. |
| 39 | type ExpandOptions struct { |
| 40 | RelativeBase string |
| 41 | SkipSchemas bool |
| 42 | ContinueOnError bool |
| 43 | } |
| 44 | |
| 45 | // ResolutionCache a cache for resolving urls |
| 46 | type ResolutionCache interface { |
| 47 | Get(string) (interface{}, bool) |
| 48 | Set(string, interface{}) |
| 49 | } |
| 50 | |
| 51 | type simpleCache struct { |
| 52 | lock sync.Mutex |
| 53 | store map[string]interface{} |
| 54 | } |
| 55 | |
| 56 | var resCache ResolutionCache |
| 57 | |
| 58 | func init() { |
| 59 | resCache = initResolutionCache() |
| 60 | } |
| 61 | |
| 62 | func initResolutionCache() ResolutionCache { |
| 63 | return &simpleCache{store: map[string]interface{}{ |
| 64 | "http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(), |
| 65 | "http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(), |
| 66 | }} |
| 67 | } |
| 68 | |
| 69 | func (s *simpleCache) Get(uri string) (interface{}, bool) { |
| 70 | debugLog("getting %q from resolution cache", uri) |
| 71 | s.lock.Lock() |
| 72 | v, ok := s.store[uri] |
| 73 | debugLog("got %q from resolution cache: %t", uri, ok) |
| 74 | |
| 75 | s.lock.Unlock() |
| 76 | return v, ok |
| 77 | } |
| 78 | |
| 79 | func (s *simpleCache) Set(uri string, data interface{}) { |
| 80 | s.lock.Lock() |
| 81 | s.store[uri] = data |
| 82 | s.lock.Unlock() |
| 83 | } |
| 84 | |
| 85 | // ResolveRefWithBase resolves a reference against a context root with preservation of base path |
| 86 | func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) { |
| 87 | resolver, err := defaultSchemaLoader(root, opts, nil) |
| 88 | if err != nil { |
| 89 | return nil, err |
| 90 | } |
| 91 | specBasePath := "" |
| 92 | if opts != nil && opts.RelativeBase != "" { |
| 93 | specBasePath, _ = absPath(opts.RelativeBase) |
| 94 | } |
| 95 | |
| 96 | result := new(Schema) |
| 97 | if err := resolver.Resolve(ref, result, specBasePath); err != nil { |
| 98 | return nil, err |
| 99 | } |
| 100 | return result, nil |
| 101 | } |
| 102 | |
| 103 | // ResolveRef resolves a reference against a context root |
| 104 | // ref is guaranteed to be in root (no need to go to external files) |
| 105 | // ResolveRef is ONLY called from the code generation module |
| 106 | func ResolveRef(root interface{}, ref *Ref) (*Schema, error) { |
| 107 | res, _, err := ref.GetPointer().Get(root) |
| 108 | if err != nil { |
| 109 | panic(err) |
| 110 | } |
| 111 | switch sch := res.(type) { |
| 112 | case Schema: |
| 113 | return &sch, nil |
| 114 | case *Schema: |
| 115 | return sch, nil |
| 116 | case map[string]interface{}: |
| 117 | b, _ := json.Marshal(sch) |
| 118 | newSch := new(Schema) |
| 119 | json.Unmarshal(b, newSch) |
| 120 | return newSch, nil |
| 121 | default: |
| 122 | return nil, fmt.Errorf("unknown type for the resolved reference") |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | // ResolveParameter resolves a paramter reference against a context root |
| 127 | func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) { |
| 128 | return ResolveParameterWithBase(root, ref, nil) |
| 129 | } |
| 130 | |
| 131 | // ResolveParameterWithBase resolves a paramter reference against a context root and base path |
| 132 | func ResolveParameterWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Parameter, error) { |
| 133 | resolver, err := defaultSchemaLoader(root, opts, nil) |
| 134 | if err != nil { |
| 135 | return nil, err |
| 136 | } |
| 137 | |
| 138 | result := new(Parameter) |
| 139 | if err := resolver.Resolve(&ref, result, ""); err != nil { |
| 140 | return nil, err |
| 141 | } |
| 142 | return result, nil |
| 143 | } |
| 144 | |
| 145 | // ResolveResponse resolves response a reference against a context root |
| 146 | func ResolveResponse(root interface{}, ref Ref) (*Response, error) { |
| 147 | return ResolveResponseWithBase(root, ref, nil) |
| 148 | } |
| 149 | |
| 150 | // ResolveResponseWithBase resolves response a reference against a context root and base path |
| 151 | func ResolveResponseWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Response, error) { |
| 152 | resolver, err := defaultSchemaLoader(root, opts, nil) |
| 153 | if err != nil { |
| 154 | return nil, err |
| 155 | } |
| 156 | |
| 157 | result := new(Response) |
| 158 | if err := resolver.Resolve(&ref, result, ""); err != nil { |
| 159 | return nil, err |
| 160 | } |
| 161 | return result, nil |
| 162 | } |
| 163 | |
| 164 | // ResolveItems resolves header and parameter items reference against a context root and base path |
| 165 | func ResolveItems(root interface{}, ref Ref, opts *ExpandOptions) (*Items, error) { |
| 166 | resolver, err := defaultSchemaLoader(root, opts, nil) |
| 167 | if err != nil { |
| 168 | return nil, err |
| 169 | } |
| 170 | basePath := "" |
| 171 | if opts.RelativeBase != "" { |
| 172 | basePath = opts.RelativeBase |
| 173 | } |
| 174 | result := new(Items) |
| 175 | if err := resolver.Resolve(&ref, result, basePath); err != nil { |
| 176 | return nil, err |
| 177 | } |
| 178 | return result, nil |
| 179 | } |
| 180 | |
| 181 | // ResolvePathItem resolves response a path item against a context root and base path |
| 182 | func ResolvePathItem(root interface{}, ref Ref, opts *ExpandOptions) (*PathItem, error) { |
| 183 | resolver, err := defaultSchemaLoader(root, opts, nil) |
| 184 | if err != nil { |
| 185 | return nil, err |
| 186 | } |
| 187 | basePath := "" |
| 188 | if opts.RelativeBase != "" { |
| 189 | basePath = opts.RelativeBase |
| 190 | } |
| 191 | result := new(PathItem) |
| 192 | if err := resolver.Resolve(&ref, result, basePath); err != nil { |
| 193 | return nil, err |
| 194 | } |
| 195 | return result, nil |
| 196 | } |
| 197 | |
| 198 | type schemaLoader struct { |
| 199 | root interface{} |
| 200 | options *ExpandOptions |
| 201 | cache ResolutionCache |
| 202 | loadDoc func(string) (json.RawMessage, error) |
| 203 | } |
| 204 | |
| 205 | var idPtr, _ = jsonpointer.New("/id") |
| 206 | var refPtr, _ = jsonpointer.New("/$ref") |
| 207 | |
| 208 | // PathLoader function to use when loading remote refs |
| 209 | var PathLoader func(string) (json.RawMessage, error) |
| 210 | |
| 211 | func init() { |
| 212 | PathLoader = func(path string) (json.RawMessage, error) { |
| 213 | data, err := swag.LoadFromFileOrHTTP(path) |
| 214 | if err != nil { |
| 215 | return nil, err |
| 216 | } |
| 217 | return json.RawMessage(data), nil |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | func defaultSchemaLoader( |
| 222 | root interface{}, |
| 223 | expandOptions *ExpandOptions, |
| 224 | cache ResolutionCache) (*schemaLoader, error) { |
| 225 | |
| 226 | if cache == nil { |
| 227 | cache = resCache |
| 228 | } |
| 229 | if expandOptions == nil { |
| 230 | expandOptions = &ExpandOptions{} |
| 231 | } |
| 232 | |
| 233 | return &schemaLoader{ |
| 234 | root: root, |
| 235 | options: expandOptions, |
| 236 | cache: cache, |
| 237 | loadDoc: func(path string) (json.RawMessage, error) { |
| 238 | debugLog("fetching document at %q", path) |
| 239 | return PathLoader(path) |
| 240 | }, |
| 241 | }, nil |
| 242 | } |
| 243 | |
| 244 | func idFromNode(node interface{}) (*Ref, error) { |
| 245 | if idValue, _, err := idPtr.Get(node); err == nil { |
| 246 | if refStr, ok := idValue.(string); ok && refStr != "" { |
| 247 | idRef, err := NewRef(refStr) |
| 248 | if err != nil { |
| 249 | return nil, err |
| 250 | } |
| 251 | return &idRef, nil |
| 252 | } |
| 253 | } |
| 254 | return nil, nil |
| 255 | } |
| 256 | |
| 257 | func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointer) *Ref { |
| 258 | if startingRef == nil { |
| 259 | return nil |
| 260 | } |
| 261 | |
| 262 | if ptr == nil { |
| 263 | return startingRef |
| 264 | } |
| 265 | |
| 266 | ret := startingRef |
| 267 | var idRef *Ref |
| 268 | node := startingNode |
| 269 | |
| 270 | for _, tok := range ptr.DecodedTokens() { |
| 271 | node, _, _ = jsonpointer.GetForToken(node, tok) |
| 272 | if node == nil { |
| 273 | break |
| 274 | } |
| 275 | |
| 276 | idRef, _ = idFromNode(node) |
| 277 | if idRef != nil { |
| 278 | nw, err := ret.Inherits(*idRef) |
| 279 | if err != nil { |
| 280 | break |
| 281 | } |
| 282 | ret = nw |
| 283 | } |
| 284 | |
| 285 | refRef, _, _ := refPtr.Get(node) |
| 286 | if refRef != nil { |
| 287 | var rf Ref |
| 288 | switch value := refRef.(type) { |
| 289 | case string: |
| 290 | rf, _ = NewRef(value) |
| 291 | } |
| 292 | nw, err := ret.Inherits(rf) |
| 293 | if err != nil { |
| 294 | break |
| 295 | } |
| 296 | nwURL := nw.GetURL() |
| 297 | if nwURL.Scheme == "file" || (nwURL.Scheme == "" && nwURL.Host == "") { |
| 298 | nwpt := filepath.ToSlash(nwURL.Path) |
| 299 | if filepath.IsAbs(nwpt) { |
| 300 | _, err := os.Stat(nwpt) |
| 301 | if err != nil { |
| 302 | nwURL.Path = filepath.Join(".", nwpt) |
| 303 | } |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | ret = nw |
| 308 | } |
| 309 | |
| 310 | } |
| 311 | |
| 312 | return ret |
| 313 | } |
| 314 | |
| 315 | func debugLog(msg string, args ...interface{}) { |
| 316 | if Debug { |
| 317 | log.Printf(msg, args...) |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | // normalize absolute path for cache. |
| 322 | // on Windows, drive letters should be converted to lower as scheme in net/url.URL |
| 323 | func normalizeAbsPath(path string) string { |
| 324 | u, err := url.Parse(path) |
| 325 | if err != nil { |
| 326 | debugLog("normalize absolute path failed: %s", err) |
| 327 | return path |
| 328 | } |
| 329 | return u.String() |
| 330 | } |
| 331 | |
| 332 | // base or refPath could be a file path or a URL |
| 333 | // given a base absolute path and a ref path, return the absolute path of refPath |
| 334 | // 1) if refPath is absolute, return it |
| 335 | // 2) if refPath is relative, join it with basePath keeping the scheme, hosts, and ports if exists |
| 336 | // base could be a directory or a full file path |
| 337 | func normalizePaths(refPath, base string) string { |
| 338 | refURL, _ := url.Parse(refPath) |
| 339 | if path.IsAbs(refURL.Path) || filepath.IsAbs(refPath) { |
| 340 | // refPath is actually absolute |
| 341 | if refURL.Host != "" { |
| 342 | return refPath |
| 343 | } |
| 344 | parts := strings.Split(refPath, "#") |
| 345 | result := filepath.FromSlash(parts[0]) |
| 346 | if len(parts) == 2 { |
| 347 | result += "#" + parts[1] |
| 348 | } |
| 349 | return result |
| 350 | } |
| 351 | |
| 352 | // relative refPath |
| 353 | baseURL, _ := url.Parse(base) |
| 354 | if !strings.HasPrefix(refPath, "#") { |
| 355 | // combining paths |
| 356 | if baseURL.Host != "" { |
| 357 | baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path) |
| 358 | } else { // base is a file |
| 359 | newBase := fmt.Sprintf("%s#%s", filepath.Join(filepath.Dir(base), filepath.FromSlash(refURL.Path)), refURL.Fragment) |
| 360 | return newBase |
| 361 | } |
| 362 | |
| 363 | } |
| 364 | // copying fragment from ref to base |
| 365 | baseURL.Fragment = refURL.Fragment |
| 366 | return baseURL.String() |
| 367 | } |
| 368 | |
| 369 | // relativeBase could be an ABSOLUTE file path or an ABSOLUTE URL |
| 370 | func normalizeFileRef(ref *Ref, relativeBase string) *Ref { |
| 371 | // This is important for when the reference is pointing to the root schema |
| 372 | if ref.String() == "" { |
| 373 | r, _ := NewRef(relativeBase) |
| 374 | return &r |
| 375 | } |
| 376 | |
| 377 | refURL := ref.GetURL() |
| 378 | debugLog("normalizing %s against %s (%s)", ref.String(), relativeBase, refURL.String()) |
| 379 | |
| 380 | s := normalizePaths(ref.String(), relativeBase) |
| 381 | r, _ := NewRef(s) |
| 382 | return &r |
| 383 | } |
| 384 | |
| 385 | func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error { |
| 386 | tgt := reflect.ValueOf(target) |
| 387 | if tgt.Kind() != reflect.Ptr { |
| 388 | return fmt.Errorf("resolve ref: target needs to be a pointer") |
| 389 | } |
| 390 | |
| 391 | refURL := ref.GetURL() |
| 392 | if refURL == nil { |
| 393 | return nil |
| 394 | } |
| 395 | |
| 396 | var res interface{} |
| 397 | var data interface{} |
| 398 | var err error |
| 399 | // Resolve against the root if it isn't nil, and if ref is pointing at the root, or has a fragment only which means |
| 400 | // it is pointing somewhere in the root. |
| 401 | root := r.root |
| 402 | if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" { |
| 403 | if baseRef, err := NewRef(basePath); err == nil { |
| 404 | root, _, _, _ = r.load(baseRef.GetURL()) |
| 405 | } |
| 406 | } |
| 407 | if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil { |
| 408 | data = root |
| 409 | } else { |
| 410 | baseRef := normalizeFileRef(ref, basePath) |
| 411 | debugLog("current ref is: %s", ref.String()) |
| 412 | debugLog("current ref normalized file: %s", baseRef.String()) |
| 413 | data, _, _, err = r.load(baseRef.GetURL()) |
| 414 | if err != nil { |
| 415 | return err |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | res = data |
| 420 | if ref.String() != "" { |
| 421 | res, _, err = ref.GetPointer().Get(data) |
| 422 | if err != nil { |
| 423 | return err |
| 424 | } |
| 425 | } |
| 426 | if err := swag.DynamicJSONToStruct(res, target); err != nil { |
| 427 | return err |
| 428 | } |
| 429 | |
| 430 | return nil |
| 431 | } |
| 432 | |
| 433 | func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) { |
| 434 | debugLog("loading schema from url: %s", refURL) |
| 435 | toFetch := *refURL |
| 436 | toFetch.Fragment = "" |
| 437 | |
| 438 | normalized := normalizeAbsPath(toFetch.String()) |
| 439 | |
| 440 | data, fromCache := r.cache.Get(normalized) |
| 441 | if !fromCache { |
| 442 | b, err := r.loadDoc(normalized) |
| 443 | if err != nil { |
| 444 | return nil, url.URL{}, false, err |
| 445 | } |
| 446 | |
| 447 | if err := json.Unmarshal(b, &data); err != nil { |
| 448 | return nil, url.URL{}, false, err |
| 449 | } |
| 450 | r.cache.Set(normalized, data) |
| 451 | } |
| 452 | |
| 453 | return data, toFetch, fromCache, nil |
| 454 | } |
| 455 | |
| 456 | // Resolve resolves a reference against basePath and stores the result in target |
| 457 | // Resolve is not in charge of following references, it only resolves ref by following its URL |
| 458 | // if the schema that ref is referring to has more refs in it. Resolve doesn't resolve them |
| 459 | // if basePath is an empty string, ref is resolved against the root schema stored in the schemaLoader struct |
| 460 | func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error { |
| 461 | return r.resolveRef(ref, target, basePath) |
| 462 | } |
| 463 | |
| 464 | // absPath returns the absolute path of a file |
| 465 | func absPath(fname string) (string, error) { |
| 466 | if strings.HasPrefix(fname, "http") { |
| 467 | return fname, nil |
| 468 | } |
| 469 | if filepath.IsAbs(fname) { |
| 470 | return fname, nil |
| 471 | } |
| 472 | wd, err := os.Getwd() |
| 473 | return filepath.Join(wd, fname), err |
| 474 | } |
| 475 | |
| 476 | // ExpandSpec expands the references in a swagger spec |
| 477 | func ExpandSpec(spec *Swagger, options *ExpandOptions) error { |
| 478 | resolver, err := defaultSchemaLoader(spec, options, nil) |
| 479 | // Just in case this ever returns an error. |
| 480 | if shouldStopOnError(err, resolver.options) { |
| 481 | return err |
| 482 | } |
| 483 | |
| 484 | // getting the base path of the spec to adjust all subsequent reference resolutions |
| 485 | specBasePath := "" |
| 486 | if options != nil && options.RelativeBase != "" { |
| 487 | specBasePath, _ = absPath(options.RelativeBase) |
| 488 | } |
| 489 | |
| 490 | if options == nil || !options.SkipSchemas { |
| 491 | for key, definition := range spec.Definitions { |
| 492 | var def *Schema |
| 493 | var err error |
| 494 | if def, err = expandSchema(definition, []string{fmt.Sprintf("#/definitions/%s", key)}, resolver, specBasePath); shouldStopOnError(err, resolver.options) { |
| 495 | return err |
| 496 | } |
| 497 | if def != nil { |
| 498 | spec.Definitions[key] = *def |
| 499 | } |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | for key, parameter := range spec.Parameters { |
| 504 | if err := expandParameter(¶meter, resolver, specBasePath); shouldStopOnError(err, resolver.options) { |
| 505 | return err |
| 506 | } |
| 507 | spec.Parameters[key] = parameter |
| 508 | } |
| 509 | |
| 510 | for key, response := range spec.Responses { |
| 511 | if err := expandResponse(&response, resolver, specBasePath); shouldStopOnError(err, resolver.options) { |
| 512 | return err |
| 513 | } |
| 514 | spec.Responses[key] = response |
| 515 | } |
| 516 | |
| 517 | if spec.Paths != nil { |
| 518 | for key, path := range spec.Paths.Paths { |
| 519 | if err := expandPathItem(&path, resolver, specBasePath); shouldStopOnError(err, resolver.options) { |
| 520 | return err |
| 521 | } |
| 522 | spec.Paths.Paths[key] = path |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | return nil |
| 527 | } |
| 528 | |
| 529 | func shouldStopOnError(err error, opts *ExpandOptions) bool { |
| 530 | if err != nil && !opts.ContinueOnError { |
| 531 | return true |
| 532 | } |
| 533 | |
| 534 | if err != nil { |
| 535 | log.Println(err) |
| 536 | } |
| 537 | |
| 538 | return false |
| 539 | } |
| 540 | |
| 541 | // ExpandSchema expands the refs in the schema object with reference to the root object |
| 542 | // go-openapi/validate uses this function |
| 543 | // notice that it is impossible to reference a json scema in a different file other than root |
| 544 | func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error { |
| 545 | // Only save the root to a tmp file if it isn't nil. |
| 546 | var base string |
| 547 | if root != nil { |
| 548 | base, _ = absPath("root") |
| 549 | if cache == nil { |
| 550 | cache = resCache |
| 551 | } |
| 552 | cache.Set(normalizeAbsPath(base), root) |
| 553 | base = "root" |
| 554 | } |
| 555 | |
| 556 | opts := &ExpandOptions{ |
| 557 | RelativeBase: base, |
| 558 | SkipSchemas: false, |
| 559 | ContinueOnError: false, |
| 560 | } |
| 561 | return ExpandSchemaWithBasePath(schema, cache, opts) |
| 562 | } |
| 563 | |
| 564 | // ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options |
| 565 | func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error { |
| 566 | if schema == nil { |
| 567 | return nil |
| 568 | } |
| 569 | |
| 570 | var basePath string |
| 571 | if opts.RelativeBase != "" { |
| 572 | basePath, _ = absPath(opts.RelativeBase) |
| 573 | } |
| 574 | |
| 575 | resolver, err := defaultSchemaLoader(nil, opts, cache) |
| 576 | if err != nil { |
| 577 | return err |
| 578 | } |
| 579 | |
| 580 | refs := []string{""} |
| 581 | var s *Schema |
| 582 | if s, err = expandSchema(*schema, refs, resolver, basePath); err != nil { |
| 583 | return err |
| 584 | } |
| 585 | *schema = *s |
| 586 | return nil |
| 587 | } |
| 588 | |
| 589 | func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) { |
| 590 | if target.Items != nil { |
| 591 | if target.Items.Schema != nil { |
| 592 | t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath) |
| 593 | if err != nil { |
| 594 | return nil, err |
| 595 | } |
| 596 | *target.Items.Schema = *t |
| 597 | } |
| 598 | for i := range target.Items.Schemas { |
| 599 | t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath) |
| 600 | if err != nil { |
| 601 | return nil, err |
| 602 | } |
| 603 | target.Items.Schemas[i] = *t |
| 604 | } |
| 605 | } |
| 606 | return &target, nil |
| 607 | } |
| 608 | |
| 609 | // basePathFromSchemaID returns a new basePath based on an existing basePath and a schema ID |
| 610 | func basePathFromSchemaID(oldBasePath, id string) string { |
| 611 | u, err := url.Parse(oldBasePath) |
| 612 | if err != nil { |
| 613 | panic(err) |
| 614 | } |
| 615 | uid, err := url.Parse(id) |
| 616 | if err != nil { |
| 617 | panic(err) |
| 618 | } |
| 619 | |
| 620 | if path.IsAbs(uid.Path) { |
| 621 | return id |
| 622 | } |
| 623 | u.Path = path.Join(path.Dir(u.Path), uid.Path) |
| 624 | return u.String() |
| 625 | } |
| 626 | |
| 627 | func isCircular(ref *Ref, basePath string, parentRefs ...string) bool { |
| 628 | return basePath != "" && swag.ContainsStringsCI(parentRefs, ref.String()) |
| 629 | } |
| 630 | |
| 631 | func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) { |
| 632 | if target.Ref.String() == "" && target.Ref.IsRoot() { |
| 633 | // normalizing is important |
| 634 | newRef := normalizeFileRef(&target.Ref, basePath) |
| 635 | target.Ref = *newRef |
| 636 | return &target, nil |
| 637 | |
| 638 | } |
| 639 | |
| 640 | /* change the base path of resolution when an ID is encountered |
| 641 | otherwise the basePath should inherit the parent's */ |
| 642 | // important: ID can be relative path |
| 643 | if target.ID != "" { |
| 644 | // handling the case when id is a folder |
| 645 | // remember that basePath has to be a file |
| 646 | refPath := target.ID |
| 647 | if strings.HasSuffix(target.ID, "/") { |
| 648 | // path.Clean here would not work correctly if basepath is http |
| 649 | refPath = fmt.Sprintf("%s%s", refPath, "placeholder.json") |
| 650 | } |
| 651 | basePath = normalizePaths(refPath, basePath) |
| 652 | } |
| 653 | |
| 654 | /* Explain here what this function does */ |
| 655 | |
| 656 | var t *Schema |
| 657 | /* if Ref is found, everything else doesn't matter */ |
| 658 | /* Ref also changes the resolution scope of children expandSchema */ |
| 659 | if target.Ref.String() != "" { |
| 660 | /* Here the resolution scope is changed because a $ref was encountered */ |
| 661 | normalizedRef := normalizeFileRef(&target.Ref, basePath) |
| 662 | normalizedBasePath := normalizedRef.RemoteURI() |
| 663 | |
| 664 | /* this means there is a circle in the recursion tree */ |
| 665 | /* return the Ref */ |
| 666 | if isCircular(normalizedRef, basePath, parentRefs...) { |
| 667 | target.Ref = *normalizedRef |
| 668 | return &target, nil |
| 669 | } |
| 670 | |
| 671 | debugLog("\nbasePath: %s", basePath) |
| 672 | if Debug { |
| 673 | b, _ := json.Marshal(target) |
| 674 | debugLog("calling Resolve with target: %s", string(b)) |
| 675 | } |
| 676 | if err := resolver.Resolve(&target.Ref, &t, basePath); shouldStopOnError(err, resolver.options) { |
| 677 | return nil, err |
| 678 | } |
| 679 | |
| 680 | if t != nil { |
| 681 | parentRefs = append(parentRefs, normalizedRef.String()) |
| 682 | var err error |
| 683 | resolver, err = transitiveResolver(basePath, target.Ref, resolver) |
| 684 | if shouldStopOnError(err, resolver.options) { |
| 685 | return nil, err |
| 686 | } |
| 687 | |
| 688 | return expandSchema(*t, parentRefs, resolver, normalizedBasePath) |
| 689 | } |
| 690 | } |
| 691 | |
| 692 | t, err := expandItems(target, parentRefs, resolver, basePath) |
| 693 | if shouldStopOnError(err, resolver.options) { |
| 694 | return &target, err |
| 695 | } |
| 696 | if t != nil { |
| 697 | target = *t |
| 698 | } |
| 699 | |
| 700 | for i := range target.AllOf { |
| 701 | t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath) |
| 702 | if shouldStopOnError(err, resolver.options) { |
| 703 | return &target, err |
| 704 | } |
| 705 | target.AllOf[i] = *t |
| 706 | } |
| 707 | for i := range target.AnyOf { |
| 708 | t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath) |
| 709 | if shouldStopOnError(err, resolver.options) { |
| 710 | return &target, err |
| 711 | } |
| 712 | target.AnyOf[i] = *t |
| 713 | } |
| 714 | for i := range target.OneOf { |
| 715 | t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath) |
| 716 | if shouldStopOnError(err, resolver.options) { |
| 717 | return &target, err |
| 718 | } |
| 719 | if t != nil { |
| 720 | target.OneOf[i] = *t |
| 721 | } |
| 722 | } |
| 723 | if target.Not != nil { |
| 724 | t, err := expandSchema(*target.Not, parentRefs, resolver, basePath) |
| 725 | if shouldStopOnError(err, resolver.options) { |
| 726 | return &target, err |
| 727 | } |
| 728 | if t != nil { |
| 729 | *target.Not = *t |
| 730 | } |
| 731 | } |
| 732 | for k := range target.Properties { |
| 733 | t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath) |
| 734 | if shouldStopOnError(err, resolver.options) { |
| 735 | return &target, err |
| 736 | } |
| 737 | if t != nil { |
| 738 | target.Properties[k] = *t |
| 739 | } |
| 740 | } |
| 741 | if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil { |
| 742 | t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath) |
| 743 | if shouldStopOnError(err, resolver.options) { |
| 744 | return &target, err |
| 745 | } |
| 746 | if t != nil { |
| 747 | *target.AdditionalProperties.Schema = *t |
| 748 | } |
| 749 | } |
| 750 | for k := range target.PatternProperties { |
| 751 | t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath) |
| 752 | if shouldStopOnError(err, resolver.options) { |
| 753 | return &target, err |
| 754 | } |
| 755 | if t != nil { |
| 756 | target.PatternProperties[k] = *t |
| 757 | } |
| 758 | } |
| 759 | for k := range target.Dependencies { |
| 760 | if target.Dependencies[k].Schema != nil { |
| 761 | t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath) |
| 762 | if shouldStopOnError(err, resolver.options) { |
| 763 | return &target, err |
| 764 | } |
| 765 | if t != nil { |
| 766 | *target.Dependencies[k].Schema = *t |
| 767 | } |
| 768 | } |
| 769 | } |
| 770 | if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil { |
| 771 | t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath) |
| 772 | if shouldStopOnError(err, resolver.options) { |
| 773 | return &target, err |
| 774 | } |
| 775 | if t != nil { |
| 776 | *target.AdditionalItems.Schema = *t |
| 777 | } |
| 778 | } |
| 779 | for k := range target.Definitions { |
| 780 | t, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath) |
| 781 | if shouldStopOnError(err, resolver.options) { |
| 782 | return &target, err |
| 783 | } |
| 784 | if t != nil { |
| 785 | target.Definitions[k] = *t |
| 786 | } |
| 787 | } |
| 788 | return &target, nil |
| 789 | } |
| 790 | |
| 791 | func derefPathItem(pathItem *PathItem, parentRefs []string, resolver *schemaLoader, basePath string) error { |
| 792 | curRef := pathItem.Ref.String() |
| 793 | if curRef != "" { |
| 794 | normalizedRef := normalizeFileRef(&pathItem.Ref, basePath) |
| 795 | normalizedBasePath := normalizedRef.RemoteURI() |
| 796 | |
| 797 | if isCircular(normalizedRef, basePath, parentRefs...) { |
| 798 | return nil |
| 799 | } |
| 800 | |
| 801 | if err := resolver.Resolve(&pathItem.Ref, pathItem, basePath); shouldStopOnError(err, resolver.options) { |
| 802 | return err |
| 803 | } |
| 804 | |
| 805 | if pathItem.Ref.String() != "" && pathItem.Ref.String() != curRef && basePath != normalizedBasePath { |
| 806 | parentRefs = append(parentRefs, normalizedRef.String()) |
| 807 | return derefPathItem(pathItem, parentRefs, resolver, normalizedBasePath) |
| 808 | } |
| 809 | } |
| 810 | |
| 811 | return nil |
| 812 | } |
| 813 | |
| 814 | func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error { |
| 815 | if pathItem == nil { |
| 816 | return nil |
| 817 | } |
| 818 | |
| 819 | parentRefs := []string{} |
| 820 | if err := derefPathItem(pathItem, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 821 | return err |
| 822 | } |
| 823 | if pathItem.Ref.String() != "" { |
| 824 | var err error |
| 825 | resolver, err = transitiveResolver(basePath, pathItem.Ref, resolver) |
| 826 | if shouldStopOnError(err, resolver.options) { |
| 827 | return err |
| 828 | } |
| 829 | } |
| 830 | pathItem.Ref = Ref{} |
| 831 | |
| 832 | parentRefs = parentRefs[0:] |
| 833 | |
| 834 | for idx := range pathItem.Parameters { |
| 835 | if err := expandParameter(&(pathItem.Parameters[idx]), resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 836 | return err |
| 837 | } |
| 838 | } |
| 839 | if err := expandOperation(pathItem.Get, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 840 | return err |
| 841 | } |
| 842 | if err := expandOperation(pathItem.Head, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 843 | return err |
| 844 | } |
| 845 | if err := expandOperation(pathItem.Options, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 846 | return err |
| 847 | } |
| 848 | if err := expandOperation(pathItem.Put, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 849 | return err |
| 850 | } |
| 851 | if err := expandOperation(pathItem.Post, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 852 | return err |
| 853 | } |
| 854 | if err := expandOperation(pathItem.Patch, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 855 | return err |
| 856 | } |
| 857 | if err := expandOperation(pathItem.Delete, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 858 | return err |
| 859 | } |
| 860 | return nil |
| 861 | } |
| 862 | |
| 863 | func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error { |
| 864 | if op == nil { |
| 865 | return nil |
| 866 | } |
| 867 | |
| 868 | for i, param := range op.Parameters { |
| 869 | if err := expandParameter(¶m, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 870 | return err |
| 871 | } |
| 872 | op.Parameters[i] = param |
| 873 | } |
| 874 | |
| 875 | if op.Responses != nil { |
| 876 | responses := op.Responses |
| 877 | if err := expandResponse(responses.Default, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 878 | return err |
| 879 | } |
| 880 | for code, response := range responses.StatusCodeResponses { |
| 881 | if err := expandResponse(&response, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 882 | return err |
| 883 | } |
| 884 | responses.StatusCodeResponses[code] = response |
| 885 | } |
| 886 | } |
| 887 | return nil |
| 888 | } |
| 889 | |
| 890 | func transitiveResolver(basePath string, ref Ref, resolver *schemaLoader) (*schemaLoader, error) { |
| 891 | if ref.IsRoot() || ref.HasFragmentOnly { |
| 892 | return resolver, nil |
| 893 | } |
| 894 | |
| 895 | baseRef, _ := NewRef(basePath) |
| 896 | currentRef := normalizeFileRef(&ref, basePath) |
| 897 | // Set a new root to resolve against |
| 898 | if !strings.HasPrefix(currentRef.String(), baseRef.String()) { |
| 899 | rootURL := currentRef.GetURL() |
| 900 | rootURL.Fragment = "" |
| 901 | root, _ := resolver.cache.Get(rootURL.String()) |
| 902 | var err error |
| 903 | resolver, err = defaultSchemaLoader(root, resolver.options, resolver.cache) |
| 904 | if err != nil { |
| 905 | return nil, err |
| 906 | } |
| 907 | } |
| 908 | |
| 909 | return resolver, nil |
| 910 | } |
| 911 | |
| 912 | // ExpandResponse expands a response based on a basepath |
| 913 | // This is the exported version of expandResponse |
| 914 | // all refs inside response will be resolved relative to basePath |
| 915 | func ExpandResponse(response *Response, basePath string) error { |
| 916 | opts := &ExpandOptions{ |
| 917 | RelativeBase: basePath, |
| 918 | } |
| 919 | resolver, err := defaultSchemaLoader(nil, opts, nil) |
| 920 | if err != nil { |
| 921 | return err |
| 922 | } |
| 923 | |
| 924 | return expandResponse(response, resolver, basePath) |
| 925 | } |
| 926 | |
| 927 | func derefResponse(response *Response, parentRefs []string, resolver *schemaLoader, basePath string) error { |
| 928 | curRef := response.Ref.String() |
| 929 | if curRef != "" { |
| 930 | /* Here the resolution scope is changed because a $ref was encountered */ |
| 931 | normalizedRef := normalizeFileRef(&response.Ref, basePath) |
| 932 | normalizedBasePath := normalizedRef.RemoteURI() |
| 933 | |
| 934 | if isCircular(normalizedRef, basePath, parentRefs...) { |
| 935 | return nil |
| 936 | } |
| 937 | |
| 938 | if err := resolver.Resolve(&response.Ref, response, basePath); shouldStopOnError(err, resolver.options) { |
| 939 | return err |
| 940 | } |
| 941 | |
| 942 | if response.Ref.String() != "" && response.Ref.String() != curRef && basePath != normalizedBasePath { |
| 943 | parentRefs = append(parentRefs, normalizedRef.String()) |
| 944 | return derefResponse(response, parentRefs, resolver, normalizedBasePath) |
| 945 | } |
| 946 | } |
| 947 | |
| 948 | return nil |
| 949 | } |
| 950 | |
| 951 | func expandResponse(response *Response, resolver *schemaLoader, basePath string) error { |
| 952 | if response == nil { |
| 953 | return nil |
| 954 | } |
| 955 | |
| 956 | parentRefs := []string{} |
| 957 | if err := derefResponse(response, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 958 | return err |
| 959 | } |
| 960 | if response.Ref.String() != "" { |
| 961 | var err error |
| 962 | resolver, err = transitiveResolver(basePath, response.Ref, resolver) |
| 963 | if shouldStopOnError(err, resolver.options) { |
| 964 | return err |
| 965 | } |
| 966 | } |
| 967 | response.Ref = Ref{} |
| 968 | |
| 969 | parentRefs = parentRefs[0:] |
| 970 | if !resolver.options.SkipSchemas && response.Schema != nil { |
| 971 | parentRefs = append(parentRefs, response.Schema.Ref.String()) |
| 972 | s, err := expandSchema(*response.Schema, parentRefs, resolver, basePath) |
| 973 | if shouldStopOnError(err, resolver.options) { |
| 974 | return err |
| 975 | } |
| 976 | *response.Schema = *s |
| 977 | } |
| 978 | |
| 979 | return nil |
| 980 | } |
| 981 | |
| 982 | // ExpandParameter expands a parameter based on a basepath |
| 983 | // This is the exported version of expandParameter |
| 984 | // all refs inside parameter will be resolved relative to basePath |
| 985 | func ExpandParameter(parameter *Parameter, basePath string) error { |
| 986 | opts := &ExpandOptions{ |
| 987 | RelativeBase: basePath, |
| 988 | } |
| 989 | resolver, err := defaultSchemaLoader(nil, opts, nil) |
| 990 | if err != nil { |
| 991 | return err |
| 992 | } |
| 993 | |
| 994 | return expandParameter(parameter, resolver, basePath) |
| 995 | } |
| 996 | |
| 997 | func derefParameter(parameter *Parameter, parentRefs []string, resolver *schemaLoader, basePath string) error { |
| 998 | curRef := parameter.Ref.String() |
| 999 | if curRef != "" { |
| 1000 | normalizedRef := normalizeFileRef(¶meter.Ref, basePath) |
| 1001 | normalizedBasePath := normalizedRef.RemoteURI() |
| 1002 | |
| 1003 | if isCircular(normalizedRef, basePath, parentRefs...) { |
| 1004 | return nil |
| 1005 | } |
| 1006 | |
| 1007 | if err := resolver.Resolve(¶meter.Ref, parameter, basePath); shouldStopOnError(err, resolver.options) { |
| 1008 | return err |
| 1009 | } |
| 1010 | |
| 1011 | if parameter.Ref.String() != "" && parameter.Ref.String() != curRef && basePath != normalizedBasePath { |
| 1012 | parentRefs = append(parentRefs, normalizedRef.String()) |
| 1013 | return derefParameter(parameter, parentRefs, resolver, normalizedBasePath) |
| 1014 | } |
| 1015 | } |
| 1016 | |
| 1017 | return nil |
| 1018 | } |
| 1019 | |
| 1020 | func expandParameter(parameter *Parameter, resolver *schemaLoader, basePath string) error { |
| 1021 | if parameter == nil { |
| 1022 | return nil |
| 1023 | } |
| 1024 | |
| 1025 | parentRefs := []string{} |
| 1026 | if err := derefParameter(parameter, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) { |
| 1027 | return err |
| 1028 | } |
| 1029 | if parameter.Ref.String() != "" { |
| 1030 | var err error |
| 1031 | resolver, err = transitiveResolver(basePath, parameter.Ref, resolver) |
| 1032 | if shouldStopOnError(err, resolver.options) { |
| 1033 | return err |
| 1034 | } |
| 1035 | } |
| 1036 | parameter.Ref = Ref{} |
| 1037 | |
| 1038 | parentRefs = parentRefs[0:] |
| 1039 | if !resolver.options.SkipSchemas && parameter.Schema != nil { |
| 1040 | parentRefs = append(parentRefs, parameter.Schema.Ref.String()) |
| 1041 | s, err := expandSchema(*parameter.Schema, parentRefs, resolver, basePath) |
| 1042 | if shouldStopOnError(err, resolver.options) { |
| 1043 | return err |
| 1044 | } |
| 1045 | *parameter.Schema = *s |
| 1046 | } |
| 1047 | return nil |
| 1048 | } |