Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / go-openapi / spec / expander.go
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         "strings"
21 )
22
23 // ExpandOptions provides options for spec expand
24 type ExpandOptions struct {
25         RelativeBase        string
26         SkipSchemas         bool
27         ContinueOnError     bool
28         AbsoluteCircularRef bool
29 }
30
31 // ResolveRefWithBase resolves a reference against a context root with preservation of base path
32 func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) {
33         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
34         if err != nil {
35                 return nil, err
36         }
37         specBasePath := ""
38         if opts != nil && opts.RelativeBase != "" {
39                 specBasePath, _ = absPath(opts.RelativeBase)
40         }
41
42         result := new(Schema)
43         if err := resolver.Resolve(ref, result, specBasePath); err != nil {
44                 return nil, err
45         }
46         return result, nil
47 }
48
49 // ResolveRef resolves a reference against a context root
50 // ref is guaranteed to be in root (no need to go to external files)
51 // ResolveRef is ONLY called from the code generation module
52 func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
53         res, _, err := ref.GetPointer().Get(root)
54         if err != nil {
55                 panic(err)
56         }
57         switch sch := res.(type) {
58         case Schema:
59                 return &sch, nil
60         case *Schema:
61                 return sch, nil
62         case map[string]interface{}:
63                 b, _ := json.Marshal(sch)
64                 newSch := new(Schema)
65                 _ = json.Unmarshal(b, newSch)
66                 return newSch, nil
67         default:
68                 return nil, fmt.Errorf("unknown type for the resolved reference")
69         }
70 }
71
72 // ResolveParameter resolves a parameter reference against a context root
73 func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
74         return ResolveParameterWithBase(root, ref, nil)
75 }
76
77 // ResolveParameterWithBase resolves a parameter reference against a context root and base path
78 func ResolveParameterWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Parameter, error) {
79         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
80         if err != nil {
81                 return nil, err
82         }
83
84         result := new(Parameter)
85         if err := resolver.Resolve(&ref, result, ""); err != nil {
86                 return nil, err
87         }
88         return result, nil
89 }
90
91 // ResolveResponse resolves response a reference against a context root
92 func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
93         return ResolveResponseWithBase(root, ref, nil)
94 }
95
96 // ResolveResponseWithBase resolves response a reference against a context root and base path
97 func ResolveResponseWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Response, error) {
98         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
99         if err != nil {
100                 return nil, err
101         }
102
103         result := new(Response)
104         if err := resolver.Resolve(&ref, result, ""); err != nil {
105                 return nil, err
106         }
107         return result, nil
108 }
109
110 // ResolveItems resolves parameter items reference against a context root and base path.
111 //
112 // NOTE: stricly speaking, this construct is not supported by Swagger 2.0.
113 // Similarly, $ref are forbidden in response headers.
114 func ResolveItems(root interface{}, ref Ref, opts *ExpandOptions) (*Items, error) {
115         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
116         if err != nil {
117                 return nil, err
118         }
119         basePath := ""
120         if opts.RelativeBase != "" {
121                 basePath = opts.RelativeBase
122         }
123         result := new(Items)
124         if err := resolver.Resolve(&ref, result, basePath); err != nil {
125                 return nil, err
126         }
127         return result, nil
128 }
129
130 // ResolvePathItem resolves response a path item against a context root and base path
131 func ResolvePathItem(root interface{}, ref Ref, opts *ExpandOptions) (*PathItem, error) {
132         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
133         if err != nil {
134                 return nil, err
135         }
136         basePath := ""
137         if opts.RelativeBase != "" {
138                 basePath = opts.RelativeBase
139         }
140         result := new(PathItem)
141         if err := resolver.Resolve(&ref, result, basePath); err != nil {
142                 return nil, err
143         }
144         return result, nil
145 }
146
147 // ExpandSpec expands the references in a swagger spec
148 func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
149         resolver, err := defaultSchemaLoader(spec, options, nil, nil)
150         // Just in case this ever returns an error.
151         if resolver.shouldStopOnError(err) {
152                 return err
153         }
154
155         // getting the base path of the spec to adjust all subsequent reference resolutions
156         specBasePath := ""
157         if options != nil && options.RelativeBase != "" {
158                 specBasePath, _ = absPath(options.RelativeBase)
159         }
160
161         if options == nil || !options.SkipSchemas {
162                 for key, definition := range spec.Definitions {
163                         var def *Schema
164                         var err error
165                         if def, err = expandSchema(definition, []string{fmt.Sprintf("#/definitions/%s", key)}, resolver, specBasePath); resolver.shouldStopOnError(err) {
166                                 return err
167                         }
168                         if def != nil {
169                                 spec.Definitions[key] = *def
170                         }
171                 }
172         }
173
174         for key := range spec.Parameters {
175                 parameter := spec.Parameters[key]
176                 if err := expandParameterOrResponse(&parameter, resolver, specBasePath); resolver.shouldStopOnError(err) {
177                         return err
178                 }
179                 spec.Parameters[key] = parameter
180         }
181
182         for key := range spec.Responses {
183                 response := spec.Responses[key]
184                 if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
185                         return err
186                 }
187                 spec.Responses[key] = response
188         }
189
190         if spec.Paths != nil {
191                 for key := range spec.Paths.Paths {
192                         path := spec.Paths.Paths[key]
193                         if err := expandPathItem(&path, resolver, specBasePath); resolver.shouldStopOnError(err) {
194                                 return err
195                         }
196                         spec.Paths.Paths[key] = path
197                 }
198         }
199
200         return nil
201 }
202
203 // baseForRoot loads in the cache the root document and produces a fake "root" base path entry
204 // for further $ref resolution
205 func baseForRoot(root interface{}, cache ResolutionCache) string {
206         // cache the root document to resolve $ref's
207         const rootBase = "root"
208         if root != nil {
209                 base, _ := absPath(rootBase)
210                 normalizedBase := normalizeAbsPath(base)
211                 debugLog("setting root doc in cache at: %s", normalizedBase)
212                 if cache == nil {
213                         cache = resCache
214                 }
215                 cache.Set(normalizedBase, root)
216                 return rootBase
217         }
218         return ""
219 }
220
221 // ExpandSchema expands the refs in the schema object with reference to the root object
222 // go-openapi/validate uses this function
223 // notice that it is impossible to reference a json schema in a different file other than root
224 func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
225         opts := &ExpandOptions{
226                 // when a root is specified, cache the root as an in-memory document for $ref retrieval
227                 RelativeBase:    baseForRoot(root, cache),
228                 SkipSchemas:     false,
229                 ContinueOnError: false,
230                 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
231                 AbsoluteCircularRef: true,
232         }
233         return ExpandSchemaWithBasePath(schema, cache, opts)
234 }
235
236 // ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options
237 func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {
238         if schema == nil {
239                 return nil
240         }
241
242         var basePath string
243         if opts.RelativeBase != "" {
244                 basePath, _ = absPath(opts.RelativeBase)
245         }
246
247         resolver, err := defaultSchemaLoader(nil, opts, cache, nil)
248         if err != nil {
249                 return err
250         }
251
252         refs := []string{""}
253         var s *Schema
254         if s, err = expandSchema(*schema, refs, resolver, basePath); err != nil {
255                 return err
256         }
257         *schema = *s
258         return nil
259 }
260
261 func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
262         if target.Items != nil {
263                 if target.Items.Schema != nil {
264                         t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
265                         if err != nil {
266                                 return nil, err
267                         }
268                         *target.Items.Schema = *t
269                 }
270                 for i := range target.Items.Schemas {
271                         t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
272                         if err != nil {
273                                 return nil, err
274                         }
275                         target.Items.Schemas[i] = *t
276                 }
277         }
278         return &target, nil
279 }
280
281 func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
282         if target.Ref.String() == "" && target.Ref.IsRoot() {
283                 // normalizing is important
284                 newRef := normalizeFileRef(&target.Ref, basePath)
285                 target.Ref = *newRef
286                 return &target, nil
287
288         }
289
290         // change the base path of resolution when an ID is encountered
291         // otherwise the basePath should inherit the parent's
292         // important: ID can be relative path
293         if target.ID != "" {
294                 debugLog("schema has ID: %s", target.ID)
295                 // handling the case when id is a folder
296                 // remember that basePath has to be a file
297                 refPath := target.ID
298                 if strings.HasSuffix(target.ID, "/") {
299                         // path.Clean here would not work correctly if basepath is http
300                         refPath = fmt.Sprintf("%s%s", refPath, "placeholder.json")
301                 }
302                 basePath = normalizePaths(refPath, basePath)
303         }
304
305         var t *Schema
306         // if Ref is found, everything else doesn't matter
307         // Ref also changes the resolution scope of children expandSchema
308         if target.Ref.String() != "" {
309                 // here the resolution scope is changed because a $ref was encountered
310                 normalizedRef := normalizeFileRef(&target.Ref, basePath)
311                 normalizedBasePath := normalizedRef.RemoteURI()
312
313                 if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
314                         // this means there is a cycle in the recursion tree: return the Ref
315                         // - circular refs cannot be expanded. We leave them as ref.
316                         // - denormalization means that a new local file ref is set relative to the original basePath
317                         debugLog("shortcut circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",
318                                 basePath, normalizedBasePath, normalizedRef.String())
319                         if !resolver.options.AbsoluteCircularRef {
320                                 target.Ref = *denormalizeFileRef(normalizedRef, normalizedBasePath, resolver.context.basePath)
321                         } else {
322                                 target.Ref = *normalizedRef
323                         }
324                         return &target, nil
325                 }
326
327                 debugLog("basePath: %s: calling Resolve with target: %#v", basePath, target)
328                 if err := resolver.Resolve(&target.Ref, &t, basePath); resolver.shouldStopOnError(err) {
329                         return nil, err
330                 }
331
332                 if t != nil {
333                         parentRefs = append(parentRefs, normalizedRef.String())
334                         var err error
335                         transitiveResolver, err := resolver.transitiveResolver(basePath, target.Ref)
336                         if transitiveResolver.shouldStopOnError(err) {
337                                 return nil, err
338                         }
339
340                         basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
341
342                         return expandSchema(*t, parentRefs, transitiveResolver, basePath)
343                 }
344         }
345
346         t, err := expandItems(target, parentRefs, resolver, basePath)
347         if resolver.shouldStopOnError(err) {
348                 return &target, err
349         }
350         if t != nil {
351                 target = *t
352         }
353
354         for i := range target.AllOf {
355                 t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
356                 if resolver.shouldStopOnError(err) {
357                         return &target, err
358                 }
359                 target.AllOf[i] = *t
360         }
361         for i := range target.AnyOf {
362                 t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
363                 if resolver.shouldStopOnError(err) {
364                         return &target, err
365                 }
366                 target.AnyOf[i] = *t
367         }
368         for i := range target.OneOf {
369                 t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
370                 if resolver.shouldStopOnError(err) {
371                         return &target, err
372                 }
373                 if t != nil {
374                         target.OneOf[i] = *t
375                 }
376         }
377         if target.Not != nil {
378                 t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
379                 if resolver.shouldStopOnError(err) {
380                         return &target, err
381                 }
382                 if t != nil {
383                         *target.Not = *t
384                 }
385         }
386         for k := range target.Properties {
387                 t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
388                 if resolver.shouldStopOnError(err) {
389                         return &target, err
390                 }
391                 if t != nil {
392                         target.Properties[k] = *t
393                 }
394         }
395         if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
396                 t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
397                 if resolver.shouldStopOnError(err) {
398                         return &target, err
399                 }
400                 if t != nil {
401                         *target.AdditionalProperties.Schema = *t
402                 }
403         }
404         for k := range target.PatternProperties {
405                 t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
406                 if resolver.shouldStopOnError(err) {
407                         return &target, err
408                 }
409                 if t != nil {
410                         target.PatternProperties[k] = *t
411                 }
412         }
413         for k := range target.Dependencies {
414                 if target.Dependencies[k].Schema != nil {
415                         t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
416                         if resolver.shouldStopOnError(err) {
417                                 return &target, err
418                         }
419                         if t != nil {
420                                 *target.Dependencies[k].Schema = *t
421                         }
422                 }
423         }
424         if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
425                 t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
426                 if resolver.shouldStopOnError(err) {
427                         return &target, err
428                 }
429                 if t != nil {
430                         *target.AdditionalItems.Schema = *t
431                 }
432         }
433         for k := range target.Definitions {
434                 t, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
435                 if resolver.shouldStopOnError(err) {
436                         return &target, err
437                 }
438                 if t != nil {
439                         target.Definitions[k] = *t
440                 }
441         }
442         return &target, nil
443 }
444
445 func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
446         if pathItem == nil {
447                 return nil
448         }
449
450         parentRefs := []string{}
451         if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {
452                 return err
453         }
454         if pathItem.Ref.String() != "" {
455                 var err error
456                 resolver, err = resolver.transitiveResolver(basePath, pathItem.Ref)
457                 if resolver.shouldStopOnError(err) {
458                         return err
459                 }
460         }
461         pathItem.Ref = Ref{}
462
463         for idx := range pathItem.Parameters {
464                 if err := expandParameterOrResponse(&(pathItem.Parameters[idx]), resolver, basePath); resolver.shouldStopOnError(err) {
465                         return err
466                 }
467         }
468         ops := []*Operation{
469                 pathItem.Get,
470                 pathItem.Head,
471                 pathItem.Options,
472                 pathItem.Put,
473                 pathItem.Post,
474                 pathItem.Patch,
475                 pathItem.Delete,
476         }
477         for _, op := range ops {
478                 if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {
479                         return err
480                 }
481         }
482         return nil
483 }
484
485 func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
486         if op == nil {
487                 return nil
488         }
489
490         for i := range op.Parameters {
491                 param := op.Parameters[i]
492                 if err := expandParameterOrResponse(&param, resolver, basePath); resolver.shouldStopOnError(err) {
493                         return err
494                 }
495                 op.Parameters[i] = param
496         }
497
498         if op.Responses != nil {
499                 responses := op.Responses
500                 if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {
501                         return err
502                 }
503                 for code := range responses.StatusCodeResponses {
504                         response := responses.StatusCodeResponses[code]
505                         if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
506                                 return err
507                         }
508                         responses.StatusCodeResponses[code] = response
509                 }
510         }
511         return nil
512 }
513
514 // ExpandResponseWithRoot expands a response based on a root document, not a fetchable document
515 func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error {
516         opts := &ExpandOptions{
517                 RelativeBase:    baseForRoot(root, cache),
518                 SkipSchemas:     false,
519                 ContinueOnError: false,
520                 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
521                 AbsoluteCircularRef: true,
522         }
523         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
524         if err != nil {
525                 return err
526         }
527
528         return expandParameterOrResponse(response, resolver, opts.RelativeBase)
529 }
530
531 // ExpandResponse expands a response based on a basepath
532 // This is the exported version of expandResponse
533 // all refs inside response will be resolved relative to basePath
534 func ExpandResponse(response *Response, basePath string) error {
535         var specBasePath string
536         if basePath != "" {
537                 specBasePath, _ = absPath(basePath)
538         }
539         opts := &ExpandOptions{
540                 RelativeBase: specBasePath,
541         }
542         resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
543         if err != nil {
544                 return err
545         }
546
547         return expandParameterOrResponse(response, resolver, opts.RelativeBase)
548 }
549
550 // ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document
551 func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error {
552         opts := &ExpandOptions{
553                 RelativeBase:    baseForRoot(root, cache),
554                 SkipSchemas:     false,
555                 ContinueOnError: false,
556                 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
557                 AbsoluteCircularRef: true,
558         }
559         resolver, err := defaultSchemaLoader(root, opts, nil, nil)
560         if err != nil {
561                 return err
562         }
563
564         return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
565 }
566
567 // ExpandParameter expands a parameter based on a basepath.
568 // This is the exported version of expandParameter
569 // all refs inside parameter will be resolved relative to basePath
570 func ExpandParameter(parameter *Parameter, basePath string) error {
571         var specBasePath string
572         if basePath != "" {
573                 specBasePath, _ = absPath(basePath)
574         }
575         opts := &ExpandOptions{
576                 RelativeBase: specBasePath,
577         }
578         resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
579         if err != nil {
580                 return err
581         }
582
583         return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
584 }
585
586 func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
587         var ref *Ref
588         var sch *Schema
589         switch refable := input.(type) {
590         case *Parameter:
591                 if refable == nil {
592                         return nil, nil, nil
593                 }
594                 ref = &refable.Ref
595                 sch = refable.Schema
596         case *Response:
597                 if refable == nil {
598                         return nil, nil, nil
599                 }
600                 ref = &refable.Ref
601                 sch = refable.Schema
602         default:
603                 return nil, nil, fmt.Errorf("expand: unsupported type %T. Input should be of type *Parameter or *Response", input)
604         }
605         return ref, sch, nil
606 }
607
608 func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
609         ref, _, err := getRefAndSchema(input)
610         if err != nil {
611                 return err
612         }
613         if ref == nil {
614                 return nil
615         }
616         parentRefs := []string{}
617         if err := resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
618                 return err
619         }
620         ref, sch, _ := getRefAndSchema(input)
621         if ref.String() != "" {
622                 transitiveResolver, err := resolver.transitiveResolver(basePath, *ref)
623                 if transitiveResolver.shouldStopOnError(err) {
624                         return err
625                 }
626                 basePath = resolver.updateBasePath(transitiveResolver, basePath)
627                 resolver = transitiveResolver
628         }
629
630         if sch != nil && sch.Ref.String() != "" {
631                 // schema expanded to a $ref in another root
632                 var ern error
633                 sch.Ref, ern = NewRef(normalizePaths(sch.Ref.String(), ref.RemoteURI()))
634                 if ern != nil {
635                         return ern
636                 }
637         }
638         if ref != nil {
639                 *ref = Ref{}
640         }
641
642         if !resolver.options.SkipSchemas && sch != nil {
643                 s, err := expandSchema(*sch, parentRefs, resolver, basePath)
644                 if resolver.shouldStopOnError(err) {
645                         return err
646                 }
647                 *sch = *s
648         }
649         return nil
650 }