blob: ad1529db5f23bb0c265281b49c616eca44415c6d [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001// 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
33var (
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.
39type ExpandOptions struct {
40 RelativeBase string
41 SkipSchemas bool
42 ContinueOnError bool
43}
44
45// ResolutionCache a cache for resolving urls
46type ResolutionCache interface {
47 Get(string) (interface{}, bool)
48 Set(string, interface{})
49}
50
51type simpleCache struct {
52 lock sync.Mutex
53 store map[string]interface{}
54}
55
56var resCache ResolutionCache
57
58func init() {
59 resCache = initResolutionCache()
60}
61
62func 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
69func (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
79func (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
86func 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
106func 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
127func 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
132func 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
146func 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
151func 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
165func 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
182func 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
198type schemaLoader struct {
199 root interface{}
200 options *ExpandOptions
201 cache ResolutionCache
202 loadDoc func(string) (json.RawMessage, error)
203}
204
205var idPtr, _ = jsonpointer.New("/id")
206var refPtr, _ = jsonpointer.New("/$ref")
207
208// PathLoader function to use when loading remote refs
209var PathLoader func(string) (json.RawMessage, error)
210
211func 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
221func 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
244func 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
257func 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
315func 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
323func 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
337func 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
370func 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
385func (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
433func (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
460func (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
465func 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
477func 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(&parameter, 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
529func 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
544func 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
565func 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
589func 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
610func 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
627func isCircular(ref *Ref, basePath string, parentRefs ...string) bool {
628 return basePath != "" && swag.ContainsStringsCI(parentRefs, ref.String())
629}
630
631func 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
791func 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
814func 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
863func 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(&param, 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
890func 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
915func 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
927func 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
951func 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
985func 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
997func derefParameter(parameter *Parameter, parentRefs []string, resolver *schemaLoader, basePath string) error {
998 curRef := parameter.Ref.String()
999 if curRef != "" {
1000 normalizedRef := normalizeFileRef(&parameter.Ref, basePath)
1001 normalizedBasePath := normalizedRef.RemoteURI()
1002
1003 if isCircular(normalizedRef, basePath, parentRefs...) {
1004 return nil
1005 }
1006
1007 if err := resolver.Resolve(&parameter.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
1020func 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}