blob: 456a9dd7efb9a293965c4f923bcc31ff15f0b8d5 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// 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
15package spec
16
17import (
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// ExpandOptions provides options for expand.
34type ExpandOptions struct {
35 RelativeBase string
36 SkipSchemas bool
37 ContinueOnError bool
38 AbsoluteCircularRef bool
39}
40
41// ResolutionCache a cache for resolving urls
42type ResolutionCache interface {
43 Get(string) (interface{}, bool)
44 Set(string, interface{})
45}
46
47type simpleCache struct {
48 lock sync.RWMutex
49 store map[string]interface{}
50}
51
52var resCache ResolutionCache
53
54func init() {
55 resCache = initResolutionCache()
56}
57
58// initResolutionCache initializes the URI resolution cache
59func initResolutionCache() ResolutionCache {
60 return &simpleCache{store: map[string]interface{}{
61 "http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(),
62 "http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(),
63 }}
64}
65
66// resolverContext allows to share a context during spec processing.
67// At the moment, it just holds the index of circular references found.
68type resolverContext struct {
69 // circulars holds all visited circular references, which allows shortcuts.
70 // NOTE: this is not just a performance improvement: it is required to figure out
71 // circular references which participate several cycles.
72 // This structure is privately instantiated and needs not be locked against
73 // concurrent access, unless we chose to implement a parallel spec walking.
74 circulars map[string]bool
75 basePath string
76}
77
78func newResolverContext(originalBasePath string) *resolverContext {
79 return &resolverContext{
80 circulars: make(map[string]bool),
81 basePath: originalBasePath, // keep the root base path in context
82 }
83}
84
85// Get retrieves a cached URI
86func (s *simpleCache) Get(uri string) (interface{}, bool) {
87 debugLog("getting %q from resolution cache", uri)
88 s.lock.RLock()
89 v, ok := s.store[uri]
90 debugLog("got %q from resolution cache: %t", uri, ok)
91
92 s.lock.RUnlock()
93 return v, ok
94}
95
96// Set caches a URI
97func (s *simpleCache) Set(uri string, data interface{}) {
98 s.lock.Lock()
99 s.store[uri] = data
100 s.lock.Unlock()
101}
102
103// ResolveRefWithBase resolves a reference against a context root with preservation of base path
104func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) {
105 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
106 if err != nil {
107 return nil, err
108 }
109 specBasePath := ""
110 if opts != nil && opts.RelativeBase != "" {
111 specBasePath, _ = absPath(opts.RelativeBase)
112 }
113
114 result := new(Schema)
115 if err := resolver.Resolve(ref, result, specBasePath); err != nil {
116 return nil, err
117 }
118 return result, nil
119}
120
121// ResolveRef resolves a reference against a context root
122// ref is guaranteed to be in root (no need to go to external files)
123// ResolveRef is ONLY called from the code generation module
124func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
125 res, _, err := ref.GetPointer().Get(root)
126 if err != nil {
127 panic(err)
128 }
129 switch sch := res.(type) {
130 case Schema:
131 return &sch, nil
132 case *Schema:
133 return sch, nil
134 case map[string]interface{}:
135 b, _ := json.Marshal(sch)
136 newSch := new(Schema)
137 _ = json.Unmarshal(b, newSch)
138 return newSch, nil
139 default:
140 return nil, fmt.Errorf("unknown type for the resolved reference")
141 }
142}
143
144// ResolveParameter resolves a parameter reference against a context root
145func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
146 return ResolveParameterWithBase(root, ref, nil)
147}
148
149// ResolveParameterWithBase resolves a parameter reference against a context root and base path
150func ResolveParameterWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Parameter, error) {
151 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
152 if err != nil {
153 return nil, err
154 }
155
156 result := new(Parameter)
157 if err := resolver.Resolve(&ref, result, ""); err != nil {
158 return nil, err
159 }
160 return result, nil
161}
162
163// ResolveResponse resolves response a reference against a context root
164func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
165 return ResolveResponseWithBase(root, ref, nil)
166}
167
168// ResolveResponseWithBase resolves response a reference against a context root and base path
169func ResolveResponseWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Response, error) {
170 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
171 if err != nil {
172 return nil, err
173 }
174
175 result := new(Response)
176 if err := resolver.Resolve(&ref, result, ""); err != nil {
177 return nil, err
178 }
179 return result, nil
180}
181
182// ResolveItems resolves header and parameter items reference against a context root and base path
183func ResolveItems(root interface{}, ref Ref, opts *ExpandOptions) (*Items, error) {
184 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
185 if err != nil {
186 return nil, err
187 }
188 basePath := ""
189 if opts.RelativeBase != "" {
190 basePath = opts.RelativeBase
191 }
192 result := new(Items)
193 if err := resolver.Resolve(&ref, result, basePath); err != nil {
194 return nil, err
195 }
196 return result, nil
197}
198
199// ResolvePathItem resolves response a path item against a context root and base path
200func ResolvePathItem(root interface{}, ref Ref, opts *ExpandOptions) (*PathItem, error) {
201 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
202 if err != nil {
203 return nil, err
204 }
205 basePath := ""
206 if opts.RelativeBase != "" {
207 basePath = opts.RelativeBase
208 }
209 result := new(PathItem)
210 if err := resolver.Resolve(&ref, result, basePath); err != nil {
211 return nil, err
212 }
213 return result, nil
214}
215
216type schemaLoader struct {
217 root interface{}
218 options *ExpandOptions
219 cache ResolutionCache
220 context *resolverContext
221 loadDoc func(string) (json.RawMessage, error)
222}
223
224var idPtr, _ = jsonpointer.New("/id")
225var refPtr, _ = jsonpointer.New("/$ref")
226
227// PathLoader function to use when loading remote refs
228var PathLoader func(string) (json.RawMessage, error)
229
230func init() {
231 PathLoader = func(path string) (json.RawMessage, error) {
232 data, err := swag.LoadFromFileOrHTTP(path)
233 if err != nil {
234 return nil, err
235 }
236 return json.RawMessage(data), nil
237 }
238}
239
240func defaultSchemaLoader(
241 root interface{},
242 expandOptions *ExpandOptions,
243 cache ResolutionCache,
244 context *resolverContext) (*schemaLoader, error) {
245
246 if cache == nil {
247 cache = resCache
248 }
249 if expandOptions == nil {
250 expandOptions = &ExpandOptions{}
251 }
252 absBase, _ := absPath(expandOptions.RelativeBase)
253 if context == nil {
254 context = newResolverContext(absBase)
255 }
256 return &schemaLoader{
257 root: root,
258 options: expandOptions,
259 cache: cache,
260 context: context,
261 loadDoc: func(path string) (json.RawMessage, error) {
262 debugLog("fetching document at %q", path)
263 return PathLoader(path)
264 },
265 }, nil
266}
267
268func idFromNode(node interface{}) (*Ref, error) {
269 if idValue, _, err := idPtr.Get(node); err == nil {
270 if refStr, ok := idValue.(string); ok && refStr != "" {
271 idRef, err := NewRef(refStr)
272 if err != nil {
273 return nil, err
274 }
275 return &idRef, nil
276 }
277 }
278 return nil, nil
279}
280
281func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointer) *Ref {
282 if startingRef == nil {
283 return nil
284 }
285
286 if ptr == nil {
287 return startingRef
288 }
289
290 ret := startingRef
291 var idRef *Ref
292 node := startingNode
293
294 for _, tok := range ptr.DecodedTokens() {
295 node, _, _ = jsonpointer.GetForToken(node, tok)
296 if node == nil {
297 break
298 }
299
300 idRef, _ = idFromNode(node)
301 if idRef != nil {
302 nw, err := ret.Inherits(*idRef)
303 if err != nil {
304 break
305 }
306 ret = nw
307 }
308
309 refRef, _, _ := refPtr.Get(node)
310 if refRef != nil {
311 var rf Ref
312 switch value := refRef.(type) {
313 case string:
314 rf, _ = NewRef(value)
315 }
316 nw, err := ret.Inherits(rf)
317 if err != nil {
318 break
319 }
320 nwURL := nw.GetURL()
321 if nwURL.Scheme == "file" || (nwURL.Scheme == "" && nwURL.Host == "") {
322 nwpt := filepath.ToSlash(nwURL.Path)
323 if filepath.IsAbs(nwpt) {
324 _, err := os.Stat(nwpt)
325 if err != nil {
326 nwURL.Path = filepath.Join(".", nwpt)
327 }
328 }
329 }
330
331 ret = nw
332 }
333
334 }
335
336 return ret
337}
338
339// normalize absolute path for cache.
340// on Windows, drive letters should be converted to lower as scheme in net/url.URL
341func normalizeAbsPath(path string) string {
342 u, err := url.Parse(path)
343 if err != nil {
344 debugLog("normalize absolute path failed: %s", err)
345 return path
346 }
347 return u.String()
348}
349
350// base or refPath could be a file path or a URL
351// given a base absolute path and a ref path, return the absolute path of refPath
352// 1) if refPath is absolute, return it
353// 2) if refPath is relative, join it with basePath keeping the scheme, hosts, and ports if exists
354// base could be a directory or a full file path
355func normalizePaths(refPath, base string) string {
356 refURL, _ := url.Parse(refPath)
357 if path.IsAbs(refURL.Path) || filepath.IsAbs(refPath) {
358 // refPath is actually absolute
359 if refURL.Host != "" {
360 return refPath
361 }
362 parts := strings.Split(refPath, "#")
363 result := filepath.FromSlash(parts[0])
364 if len(parts) == 2 {
365 result += "#" + parts[1]
366 }
367 return result
368 }
369
370 // relative refPath
371 baseURL, _ := url.Parse(base)
372 if !strings.HasPrefix(refPath, "#") {
373 // combining paths
374 if baseURL.Host != "" {
375 baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
376 } else { // base is a file
377 newBase := fmt.Sprintf("%s#%s", filepath.Join(filepath.Dir(base), filepath.FromSlash(refURL.Path)), refURL.Fragment)
378 return newBase
379 }
380
381 }
382 // copying fragment from ref to base
383 baseURL.Fragment = refURL.Fragment
384 return baseURL.String()
385}
386
387// denormalizePaths returns to simplest notation on file $ref,
388// i.e. strips the absolute path and sets a path relative to the base path.
389//
390// This is currently used when we rewrite ref after a circular ref has been detected
391func denormalizeFileRef(ref *Ref, relativeBase, originalRelativeBase string) *Ref {
392 debugLog("denormalizeFileRef for: %s", ref.String())
393
394 if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly {
395 return ref
396 }
397 // strip relativeBase from URI
398 relativeBaseURL, _ := url.Parse(relativeBase)
399 relativeBaseURL.Fragment = ""
400
401 if relativeBaseURL.IsAbs() && strings.HasPrefix(ref.String(), relativeBase) {
402 // this should work for absolute URI (e.g. http://...): we have an exact match, just trim prefix
403 r, _ := NewRef(strings.TrimPrefix(ref.String(), relativeBase))
404 return &r
405 }
406
407 if relativeBaseURL.IsAbs() {
408 // other absolute URL get unchanged (i.e. with a non-empty scheme)
409 return ref
410 }
411
412 // for relative file URIs:
413 originalRelativeBaseURL, _ := url.Parse(originalRelativeBase)
414 originalRelativeBaseURL.Fragment = ""
415 if strings.HasPrefix(ref.String(), originalRelativeBaseURL.String()) {
416 // the resulting ref is in the expanded spec: return a local ref
417 r, _ := NewRef(strings.TrimPrefix(ref.String(), originalRelativeBaseURL.String()))
418 return &r
419 }
420
421 // check if we may set a relative path, considering the original base path for this spec.
422 // Example:
423 // spec is located at /mypath/spec.json
424 // my normalized ref points to: /mypath/item.json#/target
425 // expected result: item.json#/target
426 parts := strings.Split(ref.String(), "#")
427 relativePath, err := filepath.Rel(path.Dir(originalRelativeBaseURL.String()), parts[0])
428 if err != nil {
429 // there is no common ancestor (e.g. different drives on windows)
430 // leaves the ref unchanged
431 return ref
432 }
433 if len(parts) == 2 {
434 relativePath += "#" + parts[1]
435 }
436 r, _ := NewRef(relativePath)
437 return &r
438}
439
440// relativeBase could be an ABSOLUTE file path or an ABSOLUTE URL
441func normalizeFileRef(ref *Ref, relativeBase string) *Ref {
442 // This is important for when the reference is pointing to the root schema
443 if ref.String() == "" {
444 r, _ := NewRef(relativeBase)
445 return &r
446 }
447
448 debugLog("normalizing %s against %s", ref.String(), relativeBase)
449
450 s := normalizePaths(ref.String(), relativeBase)
451 r, _ := NewRef(s)
452 return &r
453}
454
455func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error {
456 tgt := reflect.ValueOf(target)
457 if tgt.Kind() != reflect.Ptr {
458 return fmt.Errorf("resolve ref: target needs to be a pointer")
459 }
460
461 refURL := ref.GetURL()
462 if refURL == nil {
463 return nil
464 }
465
466 var res interface{}
467 var data interface{}
468 var err error
469 // Resolve against the root if it isn't nil, and if ref is pointing at the root, or has a fragment only which means
470 // it is pointing somewhere in the root.
471 root := r.root
472 if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" {
473 if baseRef, erb := NewRef(basePath); erb == nil {
474 root, _, _, _ = r.load(baseRef.GetURL())
475 }
476 }
477 if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil {
478 data = root
479 } else {
480 baseRef := normalizeFileRef(ref, basePath)
481 debugLog("current ref is: %s", ref.String())
482 debugLog("current ref normalized file: %s", baseRef.String())
483 data, _, _, err = r.load(baseRef.GetURL())
484 if err != nil {
485 return err
486 }
487 }
488
489 res = data
490 if ref.String() != "" {
491 res, _, err = ref.GetPointer().Get(data)
492 if err != nil {
493 return err
494 }
495 }
496 if err := swag.DynamicJSONToStruct(res, target); err != nil {
497 return err
498 }
499
500 return nil
501}
502
503func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
504 debugLog("loading schema from url: %s", refURL)
505 toFetch := *refURL
506 toFetch.Fragment = ""
507
508 normalized := normalizeAbsPath(toFetch.String())
509
510 data, fromCache := r.cache.Get(normalized)
511 if !fromCache {
512 b, err := r.loadDoc(normalized)
513 if err != nil {
514 return nil, url.URL{}, false, err
515 }
516
517 if err := json.Unmarshal(b, &data); err != nil {
518 return nil, url.URL{}, false, err
519 }
520 r.cache.Set(normalized, data)
521 }
522
523 return data, toFetch, fromCache, nil
524}
525
526// Resolve resolves a reference against basePath and stores the result in target
527// Resolve is not in charge of following references, it only resolves ref by following its URL
528// if the schema that ref is referring to has more refs in it. Resolve doesn't resolve them
529// if basePath is an empty string, ref is resolved against the root schema stored in the schemaLoader struct
530func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error {
531 return r.resolveRef(ref, target, basePath)
532}
533
534// absPath returns the absolute path of a file
535func absPath(fname string) (string, error) {
536 if strings.HasPrefix(fname, "http") {
537 return fname, nil
538 }
539 if filepath.IsAbs(fname) {
540 return fname, nil
541 }
542 wd, err := os.Getwd()
543 return filepath.Join(wd, fname), err
544}
545
546// ExpandSpec expands the references in a swagger spec
547func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
548 resolver, err := defaultSchemaLoader(spec, options, nil, nil)
549 // Just in case this ever returns an error.
550 if shouldStopOnError(err, resolver.options) {
551 return err
552 }
553
554 // getting the base path of the spec to adjust all subsequent reference resolutions
555 specBasePath := ""
556 if options != nil && options.RelativeBase != "" {
557 specBasePath, _ = absPath(options.RelativeBase)
558 }
559
560 if options == nil || !options.SkipSchemas {
561 for key, definition := range spec.Definitions {
562 var def *Schema
563 var err error
564 if def, err = expandSchema(definition, []string{fmt.Sprintf("#/definitions/%s", key)}, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
565 return err
566 }
567 if def != nil {
568 spec.Definitions[key] = *def
569 }
570 }
571 }
572
573 for key, parameter := range spec.Parameters {
574 if err := expandParameter(&parameter, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
575 return err
576 }
577 spec.Parameters[key] = parameter
578 }
579
580 for key, response := range spec.Responses {
581 if err := expandResponse(&response, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
582 return err
583 }
584 spec.Responses[key] = response
585 }
586
587 if spec.Paths != nil {
588 for key, path := range spec.Paths.Paths {
589 if err := expandPathItem(&path, resolver, specBasePath); shouldStopOnError(err, resolver.options) {
590 return err
591 }
592 spec.Paths.Paths[key] = path
593 }
594 }
595
596 return nil
597}
598
599func shouldStopOnError(err error, opts *ExpandOptions) bool {
600 if err != nil && !opts.ContinueOnError {
601 return true
602 }
603
604 if err != nil {
605 log.Println(err)
606 }
607
608 return false
609}
610
611// baseForRoot loads in the cache the root document and produces a fake "root" base path entry
612// for further $ref resolution
613func baseForRoot(root interface{}, cache ResolutionCache) string {
614 // cache the root document to resolve $ref's
615 const rootBase = "root"
616 if root != nil {
617 base, _ := absPath(rootBase)
618 normalizedBase := normalizeAbsPath(base)
619 debugLog("setting root doc in cache at: %s", normalizedBase)
620 if cache == nil {
621 cache = resCache
622 }
623 cache.Set(normalizedBase, root)
624 return rootBase
625 }
626 return ""
627}
628
629// ExpandSchema expands the refs in the schema object with reference to the root object
630// go-openapi/validate uses this function
631// notice that it is impossible to reference a json schema in a different file other than root
632func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
633 opts := &ExpandOptions{
634 // when a root is specified, cache the root as an in-memory document for $ref retrieval
635 RelativeBase: baseForRoot(root, cache),
636 SkipSchemas: false,
637 ContinueOnError: false,
638 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
639 AbsoluteCircularRef: true,
640 }
641 return ExpandSchemaWithBasePath(schema, cache, opts)
642}
643
644// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options
645func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {
646 if schema == nil {
647 return nil
648 }
649
650 var basePath string
651 if opts.RelativeBase != "" {
652 basePath, _ = absPath(opts.RelativeBase)
653 }
654
655 resolver, err := defaultSchemaLoader(nil, opts, cache, nil)
656 if err != nil {
657 return err
658 }
659
660 refs := []string{""}
661 var s *Schema
662 if s, err = expandSchema(*schema, refs, resolver, basePath); err != nil {
663 return err
664 }
665 *schema = *s
666 return nil
667}
668
669func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
670 if target.Items != nil {
671 if target.Items.Schema != nil {
672 t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
673 if err != nil {
674 return nil, err
675 }
676 *target.Items.Schema = *t
677 }
678 for i := range target.Items.Schemas {
679 t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
680 if err != nil {
681 return nil, err
682 }
683 target.Items.Schemas[i] = *t
684 }
685 }
686 return &target, nil
687}
688
689// basePathFromSchemaID returns a new basePath based on an existing basePath and a schema ID
690func basePathFromSchemaID(oldBasePath, id string) string {
691 u, err := url.Parse(oldBasePath)
692 if err != nil {
693 panic(err)
694 }
695 uid, err := url.Parse(id)
696 if err != nil {
697 panic(err)
698 }
699
700 if path.IsAbs(uid.Path) {
701 return id
702 }
703 u.Path = path.Join(path.Dir(u.Path), uid.Path)
704 return u.String()
705}
706
707// isCircular detects cycles in sequences of $ref.
708// It relies on a private context (which needs not be locked).
709func (r *schemaLoader) isCircular(ref *Ref, basePath string, parentRefs ...string) (foundCycle bool) {
710 normalizedRef := normalizePaths(ref.String(), basePath)
711 if _, ok := r.context.circulars[normalizedRef]; ok {
712 // circular $ref has been already detected in another explored cycle
713 foundCycle = true
714 return
715 }
716 foundCycle = swag.ContainsStringsCI(parentRefs, normalizedRef)
717 if foundCycle {
718 r.context.circulars[normalizedRef] = true
719 }
720 return
721}
722
723func updateBasePath(transitive *schemaLoader, resolver *schemaLoader, basePath string) string {
724 if transitive != resolver {
725 debugLog("got a new resolver")
726 if transitive.options != nil && transitive.options.RelativeBase != "" {
727 basePath, _ = absPath(transitive.options.RelativeBase)
728 debugLog("new basePath = %s", basePath)
729 }
730 }
731
732 return basePath
733}
734
735func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
736 if target.Ref.String() == "" && target.Ref.IsRoot() {
737 // normalizing is important
738 newRef := normalizeFileRef(&target.Ref, basePath)
739 target.Ref = *newRef
740 return &target, nil
741
742 }
743
744 /* change the base path of resolution when an ID is encountered
745 otherwise the basePath should inherit the parent's */
746 // important: ID can be relative path
747 if target.ID != "" {
748 debugLog("schema has ID: %s", target.ID)
749 // handling the case when id is a folder
750 // remember that basePath has to be a file
751 refPath := target.ID
752 if strings.HasSuffix(target.ID, "/") {
753 // path.Clean here would not work correctly if basepath is http
754 refPath = fmt.Sprintf("%s%s", refPath, "placeholder.json")
755 }
756 basePath = normalizePaths(refPath, basePath)
757 }
758
759 /* Explain here what this function does */
760 var t *Schema
761 /* if Ref is found, everything else doesn't matter */
762 /* Ref also changes the resolution scope of children expandSchema */
763 if target.Ref.String() != "" {
764 /* Here the resolution scope is changed because a $ref was encountered */
765 normalizedRef := normalizeFileRef(&target.Ref, basePath)
766 normalizedBasePath := normalizedRef.RemoteURI()
767
768 if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
769 // this means there is a cycle in the recursion tree: return the Ref
770 // - circular refs cannot be expanded. We leave them as ref.
771 // - denormalization means that a new local file ref is set relative to the original basePath
772 debugLog("shortcut circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",
773 basePath, normalizedBasePath, normalizedRef.String())
774 if !resolver.options.AbsoluteCircularRef {
775 target.Ref = *denormalizeFileRef(normalizedRef, normalizedBasePath, resolver.context.basePath)
776 } else {
777 target.Ref = *normalizedRef
778 }
779 return &target, nil
780 }
781
782 debugLog("basePath: %s", basePath)
783 if Debug {
784 b, _ := json.Marshal(target)
785 debugLog("calling Resolve with target: %s", string(b))
786 }
787 if err := resolver.Resolve(&target.Ref, &t, basePath); shouldStopOnError(err, resolver.options) {
788 return nil, err
789 }
790
791 if t != nil {
792 parentRefs = append(parentRefs, normalizedRef.String())
793 var err error
794 transitiveResolver, err := transitiveResolver(basePath, target.Ref, resolver)
795 if shouldStopOnError(err, resolver.options) {
796 return nil, err
797 }
798
799 basePath = updateBasePath(transitiveResolver, resolver, normalizedBasePath)
800
801 return expandSchema(*t, parentRefs, transitiveResolver, basePath)
802 }
803 }
804
805 t, err := expandItems(target, parentRefs, resolver, basePath)
806 if shouldStopOnError(err, resolver.options) {
807 return &target, err
808 }
809 if t != nil {
810 target = *t
811 }
812
813 for i := range target.AllOf {
814 t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
815 if shouldStopOnError(err, resolver.options) {
816 return &target, err
817 }
818 target.AllOf[i] = *t
819 }
820 for i := range target.AnyOf {
821 t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
822 if shouldStopOnError(err, resolver.options) {
823 return &target, err
824 }
825 target.AnyOf[i] = *t
826 }
827 for i := range target.OneOf {
828 t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
829 if shouldStopOnError(err, resolver.options) {
830 return &target, err
831 }
832 if t != nil {
833 target.OneOf[i] = *t
834 }
835 }
836 if target.Not != nil {
837 t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
838 if shouldStopOnError(err, resolver.options) {
839 return &target, err
840 }
841 if t != nil {
842 *target.Not = *t
843 }
844 }
845 for k := range target.Properties {
846 t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
847 if shouldStopOnError(err, resolver.options) {
848 return &target, err
849 }
850 if t != nil {
851 target.Properties[k] = *t
852 }
853 }
854 if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
855 t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
856 if shouldStopOnError(err, resolver.options) {
857 return &target, err
858 }
859 if t != nil {
860 *target.AdditionalProperties.Schema = *t
861 }
862 }
863 for k := range target.PatternProperties {
864 t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
865 if shouldStopOnError(err, resolver.options) {
866 return &target, err
867 }
868 if t != nil {
869 target.PatternProperties[k] = *t
870 }
871 }
872 for k := range target.Dependencies {
873 if target.Dependencies[k].Schema != nil {
874 t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
875 if shouldStopOnError(err, resolver.options) {
876 return &target, err
877 }
878 if t != nil {
879 *target.Dependencies[k].Schema = *t
880 }
881 }
882 }
883 if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
884 t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
885 if shouldStopOnError(err, resolver.options) {
886 return &target, err
887 }
888 if t != nil {
889 *target.AdditionalItems.Schema = *t
890 }
891 }
892 for k := range target.Definitions {
893 t, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
894 if shouldStopOnError(err, resolver.options) {
895 return &target, err
896 }
897 if t != nil {
898 target.Definitions[k] = *t
899 }
900 }
901 return &target, nil
902}
903
904func derefPathItem(pathItem *PathItem, parentRefs []string, resolver *schemaLoader, basePath string) error {
905 curRef := pathItem.Ref.String()
906 if curRef != "" {
907 normalizedRef := normalizeFileRef(&pathItem.Ref, basePath)
908 normalizedBasePath := normalizedRef.RemoteURI()
909
910 if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
911 return nil
912 }
913
914 if err := resolver.Resolve(&pathItem.Ref, pathItem, basePath); shouldStopOnError(err, resolver.options) {
915 return err
916 }
917
918 if pathItem.Ref.String() != "" && pathItem.Ref.String() != curRef && basePath != normalizedBasePath {
919 parentRefs = append(parentRefs, normalizedRef.String())
920 return derefPathItem(pathItem, parentRefs, resolver, normalizedBasePath)
921 }
922 }
923
924 return nil
925}
926
927func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
928 if pathItem == nil {
929 return nil
930 }
931
932 parentRefs := []string{}
933 if err := derefPathItem(pathItem, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
934 return err
935 }
936 if pathItem.Ref.String() != "" {
937 var err error
938 resolver, err = transitiveResolver(basePath, pathItem.Ref, resolver)
939 if shouldStopOnError(err, resolver.options) {
940 return err
941 }
942 }
943 pathItem.Ref = Ref{}
944
945 // Currently unused:
946 //parentRefs = parentRefs[0:]
947
948 for idx := range pathItem.Parameters {
949 if err := expandParameter(&(pathItem.Parameters[idx]), resolver, basePath); shouldStopOnError(err, resolver.options) {
950 return err
951 }
952 }
953 if err := expandOperation(pathItem.Get, resolver, basePath); shouldStopOnError(err, resolver.options) {
954 return err
955 }
956 if err := expandOperation(pathItem.Head, resolver, basePath); shouldStopOnError(err, resolver.options) {
957 return err
958 }
959 if err := expandOperation(pathItem.Options, resolver, basePath); shouldStopOnError(err, resolver.options) {
960 return err
961 }
962 if err := expandOperation(pathItem.Put, resolver, basePath); shouldStopOnError(err, resolver.options) {
963 return err
964 }
965 if err := expandOperation(pathItem.Post, resolver, basePath); shouldStopOnError(err, resolver.options) {
966 return err
967 }
968 if err := expandOperation(pathItem.Patch, resolver, basePath); shouldStopOnError(err, resolver.options) {
969 return err
970 }
971 if err := expandOperation(pathItem.Delete, resolver, basePath); shouldStopOnError(err, resolver.options) {
972 return err
973 }
974 return nil
975}
976
977func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
978 if op == nil {
979 return nil
980 }
981
982 for i, param := range op.Parameters {
983 if err := expandParameter(&param, resolver, basePath); shouldStopOnError(err, resolver.options) {
984 return err
985 }
986 op.Parameters[i] = param
987 }
988
989 if op.Responses != nil {
990 responses := op.Responses
991 if err := expandResponse(responses.Default, resolver, basePath); shouldStopOnError(err, resolver.options) {
992 return err
993 }
994 for code, response := range responses.StatusCodeResponses {
995 if err := expandResponse(&response, resolver, basePath); shouldStopOnError(err, resolver.options) {
996 return err
997 }
998 responses.StatusCodeResponses[code] = response
999 }
1000 }
1001 return nil
1002}
1003
1004func transitiveResolver(basePath string, ref Ref, resolver *schemaLoader) (*schemaLoader, error) {
1005 if ref.IsRoot() || ref.HasFragmentOnly {
1006 return resolver, nil
1007 }
1008
1009 baseRef, _ := NewRef(basePath)
1010 currentRef := normalizeFileRef(&ref, basePath)
1011 // Set a new root to resolve against
1012 if !strings.HasPrefix(currentRef.String(), baseRef.String()) {
1013 rootURL := currentRef.GetURL()
1014 rootURL.Fragment = ""
1015 root, _ := resolver.cache.Get(rootURL.String())
1016 var err error
1017
1018 // shallow copy of resolver options to set a new RelativeBase when
1019 // traversing multiple documents
1020 newOptions := resolver.options
1021 newOptions.RelativeBase = rootURL.String()
1022 debugLog("setting new root: %s", newOptions.RelativeBase)
1023 resolver, err = defaultSchemaLoader(root, newOptions, resolver.cache, resolver.context)
1024 if err != nil {
1025 return nil, err
1026 }
1027 }
1028
1029 return resolver, nil
1030}
1031
1032// ExpandResponseWithRoot expands a response based on a root document, not a fetchable document
1033func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error {
1034 opts := &ExpandOptions{
1035 RelativeBase: baseForRoot(root, cache),
1036 SkipSchemas: false,
1037 ContinueOnError: false,
1038 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
1039 AbsoluteCircularRef: true,
1040 }
1041 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
1042 if err != nil {
1043 return err
1044 }
1045
1046 return expandResponse(response, resolver, opts.RelativeBase)
1047}
1048
1049// ExpandResponse expands a response based on a basepath
1050// This is the exported version of expandResponse
1051// all refs inside response will be resolved relative to basePath
1052func ExpandResponse(response *Response, basePath string) error {
1053 var specBasePath string
1054 if basePath != "" {
1055 specBasePath, _ = absPath(basePath)
1056 }
1057 opts := &ExpandOptions{
1058 RelativeBase: specBasePath,
1059 }
1060 resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
1061 if err != nil {
1062 return err
1063 }
1064
1065 return expandResponse(response, resolver, opts.RelativeBase)
1066}
1067
1068func derefResponse(response *Response, parentRefs []string, resolver *schemaLoader, basePath string) error {
1069 curRef := response.Ref.String()
1070 if curRef != "" {
1071 /* Here the resolution scope is changed because a $ref was encountered */
1072 normalizedRef := normalizeFileRef(&response.Ref, basePath)
1073 normalizedBasePath := normalizedRef.RemoteURI()
1074
1075 if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
1076 return nil
1077 }
1078
1079 if err := resolver.Resolve(&response.Ref, response, basePath); shouldStopOnError(err, resolver.options) {
1080 return err
1081 }
1082
1083 if response.Ref.String() != "" && response.Ref.String() != curRef && basePath != normalizedBasePath {
1084 parentRefs = append(parentRefs, normalizedRef.String())
1085 return derefResponse(response, parentRefs, resolver, normalizedBasePath)
1086 }
1087 }
1088
1089 return nil
1090}
1091
1092func expandResponse(response *Response, resolver *schemaLoader, basePath string) error {
1093 if response == nil {
1094 return nil
1095 }
1096 parentRefs := []string{}
1097 if err := derefResponse(response, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
1098 return err
1099 }
1100 if response.Ref.String() != "" {
1101 transitiveResolver, err := transitiveResolver(basePath, response.Ref, resolver)
1102 if shouldStopOnError(err, transitiveResolver.options) {
1103 return err
1104 }
1105 basePath = updateBasePath(transitiveResolver, resolver, basePath)
1106 resolver = transitiveResolver
1107 }
1108 if response.Schema != nil && response.Schema.Ref.String() != "" {
1109 // schema expanded to a $ref in another root
1110 var ern error
1111 response.Schema.Ref, ern = NewRef(normalizePaths(response.Schema.Ref.String(), response.Ref.RemoteURI()))
1112 if ern != nil {
1113 return ern
1114 }
1115 }
1116 response.Ref = Ref{}
1117
1118 parentRefs = parentRefs[0:]
1119 if !resolver.options.SkipSchemas && response.Schema != nil {
1120 // parentRefs = append(parentRefs, response.Schema.Ref.String())
1121 s, err := expandSchema(*response.Schema, parentRefs, resolver, basePath)
1122 if shouldStopOnError(err, resolver.options) {
1123 return err
1124 }
1125 *response.Schema = *s
1126 }
1127
1128 return nil
1129}
1130
1131// ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document
1132func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error {
1133 opts := &ExpandOptions{
1134 RelativeBase: baseForRoot(root, cache),
1135 SkipSchemas: false,
1136 ContinueOnError: false,
1137 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
1138 AbsoluteCircularRef: true,
1139 }
1140 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
1141 if err != nil {
1142 return err
1143 }
1144
1145 return expandParameter(parameter, resolver, opts.RelativeBase)
1146}
1147
1148// ExpandParameter expands a parameter based on a basepath
1149// This is the exported version of expandParameter
1150// all refs inside parameter will be resolved relative to basePath
1151func ExpandParameter(parameter *Parameter, basePath string) error {
1152 var specBasePath string
1153 if basePath != "" {
1154 specBasePath, _ = absPath(basePath)
1155 }
1156 opts := &ExpandOptions{
1157 RelativeBase: specBasePath,
1158 }
1159 resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
1160 if err != nil {
1161 return err
1162 }
1163
1164 return expandParameter(parameter, resolver, opts.RelativeBase)
1165}
1166
1167func derefParameter(parameter *Parameter, parentRefs []string, resolver *schemaLoader, basePath string) error {
1168 curRef := parameter.Ref.String()
1169 if curRef != "" {
1170 normalizedRef := normalizeFileRef(&parameter.Ref, basePath)
1171 normalizedBasePath := normalizedRef.RemoteURI()
1172
1173 if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
1174 return nil
1175 }
1176
1177 if err := resolver.Resolve(&parameter.Ref, parameter, basePath); shouldStopOnError(err, resolver.options) {
1178 return err
1179 }
1180
1181 if parameter.Ref.String() != "" && parameter.Ref.String() != curRef && basePath != normalizedBasePath {
1182 parentRefs = append(parentRefs, normalizedRef.String())
1183 return derefParameter(parameter, parentRefs, resolver, normalizedBasePath)
1184 }
1185 }
1186
1187 return nil
1188}
1189
1190func expandParameter(parameter *Parameter, resolver *schemaLoader, basePath string) error {
1191 if parameter == nil {
1192 return nil
1193 }
1194
1195 parentRefs := []string{}
1196 if err := derefParameter(parameter, parentRefs, resolver, basePath); shouldStopOnError(err, resolver.options) {
1197 return err
1198 }
1199 if parameter.Ref.String() != "" {
1200 transitiveResolver, err := transitiveResolver(basePath, parameter.Ref, resolver)
1201 if shouldStopOnError(err, transitiveResolver.options) {
1202 return err
1203 }
1204 basePath = updateBasePath(transitiveResolver, resolver, basePath)
1205 resolver = transitiveResolver
1206 }
1207
1208 if parameter.Schema != nil && parameter.Schema.Ref.String() != "" {
1209 // schema expanded to a $ref in another root
1210 var ern error
1211 parameter.Schema.Ref, ern = NewRef(normalizePaths(parameter.Schema.Ref.String(), parameter.Ref.RemoteURI()))
1212 if ern != nil {
1213 return ern
1214 }
1215 }
1216 parameter.Ref = Ref{}
1217
1218 parentRefs = parentRefs[0:]
1219 if !resolver.options.SkipSchemas && parameter.Schema != nil {
1220 s, err := expandSchema(*parameter.Schema, parentRefs, resolver, basePath)
1221 if shouldStopOnError(err, resolver.options) {
1222 return err
1223 }
1224 *parameter.Schema = *s
1225 }
1226 return nil
1227}