podman
594 строки · 16.7 Кб
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
15package spec16
17import (18"encoding/json"19"fmt"20)
21
22// ExpandOptions provides options for the spec expander.
23//
24// RelativeBase is the path to the root document. This can be a remote URL or a path to a local file.
25//
26// If left empty, the root document is assumed to be located in the current working directory:
27// all relative $ref's will be resolved from there.
28//
29// PathLoader injects a document loading method. By default, this resolves to the function provided by the SpecLoader package variable.
30//
31type ExpandOptions struct {32RelativeBase string // the path to the root document to expand. This is a file, not a directory33SkipSchemas bool // do not expand schemas, just paths, parameters and responses34ContinueOnError bool // continue expanding even after and error is found35PathLoader func(string) (json.RawMessage, error) `json:"-"` // the document loading method that takes a path as input and yields a json document36AbsoluteCircularRef bool // circular $ref remaining after expansion remain absolute URLs37}
38
39func optionsOrDefault(opts *ExpandOptions) *ExpandOptions {40if opts != nil {41clone := *opts // shallow clone to avoid internal changes to be propagated to the caller42if clone.RelativeBase != "" {43clone.RelativeBase = normalizeBase(clone.RelativeBase)44}45// if the relative base is empty, let the schema loader choose a pseudo root document46return &clone47}48return &ExpandOptions{}49}
50
51// ExpandSpec expands the references in a swagger spec
52func ExpandSpec(spec *Swagger, options *ExpandOptions) error {53options = optionsOrDefault(options)54resolver := defaultSchemaLoader(spec, options, nil, nil)55
56specBasePath := options.RelativeBase57
58if !options.SkipSchemas {59for key, definition := range spec.Definitions {60parentRefs := make([]string, 0, 10)61parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key))62
63def, err := expandSchema(definition, parentRefs, resolver, specBasePath)64if resolver.shouldStopOnError(err) {65return err66}67if def != nil {68spec.Definitions[key] = *def69}70}71}72
73for key := range spec.Parameters {74parameter := spec.Parameters[key]75if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) {76return err77}78spec.Parameters[key] = parameter79}80
81for key := range spec.Responses {82response := spec.Responses[key]83if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {84return err85}86spec.Responses[key] = response87}88
89if spec.Paths != nil {90for key := range spec.Paths.Paths {91pth := spec.Paths.Paths[key]92if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) {93return err94}95spec.Paths.Paths[key] = pth96}97}98
99return nil100}
101
102const rootBase = ".root"103
104// baseForRoot loads in the cache the root document and produces a fake ".root" base path entry
105// for further $ref resolution
106//
107// Setting the cache is optional and this parameter may safely be left to nil.
108func baseForRoot(root interface{}, cache ResolutionCache) string {109if root == nil {110return ""111}112
113// cache the root document to resolve $ref's114normalizedBase := normalizeBase(rootBase)115cache.Set(normalizedBase, root)116
117return normalizedBase118}
119
120// ExpandSchema expands the refs in the schema object with reference to the root object.
121//
122// go-openapi/validate uses this function.
123//
124// Notice that it is impossible to reference a json schema in a different document other than root
125// (use ExpandSchemaWithBasePath to resolve external references).
126//
127// Setting the cache is optional and this parameter may safely be left to nil.
128func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {129cache = cacheOrDefault(cache)130if root == nil {131root = schema132}133
134opts := &ExpandOptions{135// when a root is specified, cache the root as an in-memory document for $ref retrieval136RelativeBase: baseForRoot(root, cache),137SkipSchemas: false,138ContinueOnError: false,139}140
141return ExpandSchemaWithBasePath(schema, cache, opts)142}
143
144// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options.
145//
146// Setting the cache is optional and this parameter may safely be left to nil.
147func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {148if schema == nil {149return nil150}151
152cache = cacheOrDefault(cache)153
154opts = optionsOrDefault(opts)155
156resolver := defaultSchemaLoader(nil, opts, cache, nil)157
158parentRefs := make([]string, 0, 10)159s, err := expandSchema(*schema, parentRefs, resolver, opts.RelativeBase)160if err != nil {161return err162}163if s != nil {164// guard for when continuing on error165*schema = *s166}167
168return nil169}
170
171func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {172if target.Items == nil {173return &target, nil174}175
176// array177if target.Items.Schema != nil {178t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)179if err != nil {180return nil, err181}182*target.Items.Schema = *t183}184
185// tuple186for i := range target.Items.Schemas {187t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)188if err != nil {189return nil, err190}191target.Items.Schemas[i] = *t192}193
194return &target, nil195}
196
197func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {198if target.Ref.String() == "" && target.Ref.IsRoot() {199newRef := normalizeRef(&target.Ref, basePath)200target.Ref = *newRef201return &target, nil202}203
204// change the base path of resolution when an ID is encountered205// otherwise the basePath should inherit the parent's206if target.ID != "" {207basePath, _ = resolver.setSchemaID(target, target.ID, basePath)208}209
210if target.Ref.String() != "" {211return expandSchemaRef(target, parentRefs, resolver, basePath)212}213
214for k := range target.Definitions {215tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)216if resolver.shouldStopOnError(err) {217return &target, err218}219if tt != nil {220target.Definitions[k] = *tt221}222}223
224t, err := expandItems(target, parentRefs, resolver, basePath)225if resolver.shouldStopOnError(err) {226return &target, err227}228if t != nil {229target = *t230}231
232for i := range target.AllOf {233t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)234if resolver.shouldStopOnError(err) {235return &target, err236}237if t != nil {238target.AllOf[i] = *t239}240}241
242for i := range target.AnyOf {243t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)244if resolver.shouldStopOnError(err) {245return &target, err246}247if t != nil {248target.AnyOf[i] = *t249}250}251
252for i := range target.OneOf {253t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)254if resolver.shouldStopOnError(err) {255return &target, err256}257if t != nil {258target.OneOf[i] = *t259}260}261
262if target.Not != nil {263t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)264if resolver.shouldStopOnError(err) {265return &target, err266}267if t != nil {268*target.Not = *t269}270}271
272for k := range target.Properties {273t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)274if resolver.shouldStopOnError(err) {275return &target, err276}277if t != nil {278target.Properties[k] = *t279}280}281
282if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {283t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)284if resolver.shouldStopOnError(err) {285return &target, err286}287if t != nil {288*target.AdditionalProperties.Schema = *t289}290}291
292for k := range target.PatternProperties {293t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)294if resolver.shouldStopOnError(err) {295return &target, err296}297if t != nil {298target.PatternProperties[k] = *t299}300}301
302for k := range target.Dependencies {303if target.Dependencies[k].Schema != nil {304t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)305if resolver.shouldStopOnError(err) {306return &target, err307}308if t != nil {309*target.Dependencies[k].Schema = *t310}311}312}313
314if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {315t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)316if resolver.shouldStopOnError(err) {317return &target, err318}319if t != nil {320*target.AdditionalItems.Schema = *t321}322}323return &target, nil324}
325
326func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {327// if a Ref is found, all sibling fields are skipped328// Ref also changes the resolution scope of children expandSchema329
330// here the resolution scope is changed because a $ref was encountered331normalizedRef := normalizeRef(&target.Ref, basePath)332normalizedBasePath := normalizedRef.RemoteURI()333
334if resolver.isCircular(normalizedRef, basePath, parentRefs...) {335// this means there is a cycle in the recursion tree: return the Ref336// - circular refs cannot be expanded. We leave them as ref.337// - denormalization means that a new local file ref is set relative to the original basePath338debugLog("short circuit circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",339basePath, normalizedBasePath, normalizedRef.String())340if !resolver.options.AbsoluteCircularRef {341target.Ref = denormalizeRef(normalizedRef, resolver.context.basePath, resolver.context.rootID)342} else {343target.Ref = *normalizedRef344}345return &target, nil346}347
348var t *Schema349err := resolver.Resolve(&target.Ref, &t, basePath)350if resolver.shouldStopOnError(err) {351return nil, err352}353
354if t == nil {355// guard for when continuing on error356return &target, nil357}358
359parentRefs = append(parentRefs, normalizedRef.String())360transitiveResolver := resolver.transitiveResolver(basePath, target.Ref)361
362basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)363
364return expandSchema(*t, parentRefs, transitiveResolver, basePath)365}
366
367func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {368if pathItem == nil {369return nil370}371
372parentRefs := make([]string, 0, 10)373if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {374return err375}376
377if pathItem.Ref.String() != "" {378transitiveResolver := resolver.transitiveResolver(basePath, pathItem.Ref)379basePath = transitiveResolver.updateBasePath(resolver, basePath)380resolver = transitiveResolver381}382
383pathItem.Ref = Ref{}384for i := range pathItem.Parameters {385if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) {386return err387}388}389
390ops := []*Operation{391pathItem.Get,392pathItem.Head,393pathItem.Options,394pathItem.Put,395pathItem.Post,396pathItem.Patch,397pathItem.Delete,398}399for _, op := range ops {400if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {401return err402}403}404
405return nil406}
407
408func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {409if op == nil {410return nil411}412
413for i := range op.Parameters {414param := op.Parameters[i]415if err := expandParameterOrResponse(¶m, resolver, basePath); resolver.shouldStopOnError(err) {416return err417}418op.Parameters[i] = param419}420
421if op.Responses == nil {422return nil423}424
425responses := op.Responses426if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {427return err428}429
430for code := range responses.StatusCodeResponses {431response := responses.StatusCodeResponses[code]432if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {433return err434}435responses.StatusCodeResponses[code] = response436}437
438return nil439}
440
441// ExpandResponseWithRoot expands a response based on a root document, not a fetchable document
442//
443// Notice that it is impossible to reference a json schema in a different document other than root
444// (use ExpandResponse to resolve external references).
445//
446// Setting the cache is optional and this parameter may safely be left to nil.
447func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error {448cache = cacheOrDefault(cache)449opts := &ExpandOptions{450RelativeBase: baseForRoot(root, cache),451}452resolver := defaultSchemaLoader(root, opts, cache, nil)453
454return expandParameterOrResponse(response, resolver, opts.RelativeBase)455}
456
457// ExpandResponse expands a response based on a basepath
458//
459// All refs inside response will be resolved relative to basePath
460func ExpandResponse(response *Response, basePath string) error {461opts := optionsOrDefault(&ExpandOptions{462RelativeBase: basePath,463})464resolver := defaultSchemaLoader(nil, opts, nil, nil)465
466return expandParameterOrResponse(response, resolver, opts.RelativeBase)467}
468
469// ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document.
470//
471// Notice that it is impossible to reference a json schema in a different document other than root
472// (use ExpandParameter to resolve external references).
473func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error {474cache = cacheOrDefault(cache)475
476opts := &ExpandOptions{477RelativeBase: baseForRoot(root, cache),478}479resolver := defaultSchemaLoader(root, opts, cache, nil)480
481return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)482}
483
484// ExpandParameter expands a parameter based on a basepath.
485// This is the exported version of expandParameter
486// all refs inside parameter will be resolved relative to basePath
487func ExpandParameter(parameter *Parameter, basePath string) error {488opts := optionsOrDefault(&ExpandOptions{489RelativeBase: basePath,490})491resolver := defaultSchemaLoader(nil, opts, nil, nil)492
493return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)494}
495
496func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {497var (498ref *Ref499sch *Schema500)501
502switch refable := input.(type) {503case *Parameter:504if refable == nil {505return nil, nil, nil506}507ref = &refable.Ref508sch = refable.Schema509case *Response:510if refable == nil {511return nil, nil, nil512}513ref = &refable.Ref514sch = refable.Schema515default:516return nil, nil, fmt.Errorf("unsupported type: %T: %w", input, ErrExpandUnsupportedType)517}518
519return ref, sch, nil520}
521
522func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {523ref, _, err := getRefAndSchema(input)524if err != nil {525return err526}527
528if ref == nil {529return nil530}531
532parentRefs := make([]string, 0, 10)533if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {534return err535}536
537ref, sch, _ := getRefAndSchema(input)538if ref.String() != "" {539transitiveResolver := resolver.transitiveResolver(basePath, *ref)540basePath = resolver.updateBasePath(transitiveResolver, basePath)541resolver = transitiveResolver542}543
544if sch == nil {545// nothing to be expanded546if ref != nil {547*ref = Ref{}548}549return nil550}551
552if sch.Ref.String() != "" {553rebasedRef, ern := NewRef(normalizeURI(sch.Ref.String(), basePath))554if ern != nil {555return ern556}557
558switch {559case resolver.isCircular(&rebasedRef, basePath, parentRefs...):560// this is a circular $ref: stop expansion561if !resolver.options.AbsoluteCircularRef {562sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)563} else {564sch.Ref = rebasedRef565}566case !resolver.options.SkipSchemas:567// schema expanded to a $ref in another root568sch.Ref = rebasedRef569debugLog("rebased to: %s", sch.Ref.String())570default:571// skip schema expansion but rebase $ref to schema572sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)573}574}575
576if ref != nil {577*ref = Ref{}578}579
580// expand schema581if !resolver.options.SkipSchemas {582s, err := expandSchema(*sch, parentRefs, resolver, basePath)583if resolver.shouldStopOnError(err) {584return err585}586if s == nil {587// guard for when continuing on error588return nil589}590*sch = *s591}592
593return nil594}
595