1 // Copyright 2015 go-swagger maintainers
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
23 // ExpandOptions provides options for spec expand
24 type ExpandOptions struct {
28 AbsoluteCircularRef bool
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)
38 if opts != nil && opts.RelativeBase != "" {
39 specBasePath, _ = absPath(opts.RelativeBase)
43 if err := resolver.Resolve(ref, result, specBasePath); err != nil {
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)
57 switch sch := res.(type) {
62 case map[string]interface{}:
63 b, _ := json.Marshal(sch)
65 _ = json.Unmarshal(b, newSch)
68 return nil, fmt.Errorf("unknown type for the resolved reference")
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)
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)
84 result := new(Parameter)
85 if err := resolver.Resolve(&ref, result, ""); err != nil {
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)
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)
103 result := new(Response)
104 if err := resolver.Resolve(&ref, result, ""); err != nil {
110 // ResolveItems resolves parameter items reference against a context root and base path.
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)
120 if opts.RelativeBase != "" {
121 basePath = opts.RelativeBase
124 if err := resolver.Resolve(&ref, result, basePath); err != nil {
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)
137 if opts.RelativeBase != "" {
138 basePath = opts.RelativeBase
140 result := new(PathItem)
141 if err := resolver.Resolve(&ref, result, basePath); err != nil {
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) {
155 // getting the base path of the spec to adjust all subsequent reference resolutions
157 if options != nil && options.RelativeBase != "" {
158 specBasePath, _ = absPath(options.RelativeBase)
161 if options == nil || !options.SkipSchemas {
162 for key, definition := range spec.Definitions {
165 if def, err = expandSchema(definition, []string{fmt.Sprintf("#/definitions/%s", key)}, resolver, specBasePath); resolver.shouldStopOnError(err) {
169 spec.Definitions[key] = *def
174 for key := range spec.Parameters {
175 parameter := spec.Parameters[key]
176 if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) {
179 spec.Parameters[key] = parameter
182 for key := range spec.Responses {
183 response := spec.Responses[key]
184 if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
187 spec.Responses[key] = response
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) {
196 spec.Paths.Paths[key] = path
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"
209 base, _ := absPath(rootBase)
210 normalizedBase := normalizeAbsPath(base)
211 debugLog("setting root doc in cache at: %s", normalizedBase)
215 cache.Set(normalizedBase, root)
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),
229 ContinueOnError: false,
230 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
231 AbsoluteCircularRef: true,
233 return ExpandSchemaWithBasePath(schema, cache, opts)
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 {
243 if opts.RelativeBase != "" {
244 basePath, _ = absPath(opts.RelativeBase)
247 resolver, err := defaultSchemaLoader(nil, opts, cache, nil)
254 if s, err = expandSchema(*schema, refs, resolver, basePath); err != nil {
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)
268 *target.Items.Schema = *t
270 for i := range target.Items.Schemas {
271 t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
275 target.Items.Schemas[i] = *t
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)
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
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
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")
302 basePath = normalizePaths(refPath, basePath)
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()
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)
322 target.Ref = *normalizedRef
327 debugLog("basePath: %s: calling Resolve with target: %#v", basePath, target)
328 if err := resolver.Resolve(&target.Ref, &t, basePath); resolver.shouldStopOnError(err) {
333 parentRefs = append(parentRefs, normalizedRef.String())
335 transitiveResolver, err := resolver.transitiveResolver(basePath, target.Ref)
336 if transitiveResolver.shouldStopOnError(err) {
340 basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
342 return expandSchema(*t, parentRefs, transitiveResolver, basePath)
346 t, err := expandItems(target, parentRefs, resolver, basePath)
347 if resolver.shouldStopOnError(err) {
354 for i := range target.AllOf {
355 t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
356 if resolver.shouldStopOnError(err) {
361 for i := range target.AnyOf {
362 t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
363 if resolver.shouldStopOnError(err) {
368 for i := range target.OneOf {
369 t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
370 if resolver.shouldStopOnError(err) {
377 if target.Not != nil {
378 t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
379 if resolver.shouldStopOnError(err) {
386 for k := range target.Properties {
387 t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
388 if resolver.shouldStopOnError(err) {
392 target.Properties[k] = *t
395 if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
396 t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
397 if resolver.shouldStopOnError(err) {
401 *target.AdditionalProperties.Schema = *t
404 for k := range target.PatternProperties {
405 t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
406 if resolver.shouldStopOnError(err) {
410 target.PatternProperties[k] = *t
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) {
420 *target.Dependencies[k].Schema = *t
424 if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
425 t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
426 if resolver.shouldStopOnError(err) {
430 *target.AdditionalItems.Schema = *t
433 for k := range target.Definitions {
434 t, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
435 if resolver.shouldStopOnError(err) {
439 target.Definitions[k] = *t
445 func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
450 parentRefs := []string{}
451 if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {
454 if pathItem.Ref.String() != "" {
456 resolver, err = resolver.transitiveResolver(basePath, pathItem.Ref)
457 if resolver.shouldStopOnError(err) {
463 for idx := range pathItem.Parameters {
464 if err := expandParameterOrResponse(&(pathItem.Parameters[idx]), resolver, basePath); resolver.shouldStopOnError(err) {
477 for _, op := range ops {
478 if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {
485 func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
490 for i := range op.Parameters {
491 param := op.Parameters[i]
492 if err := expandParameterOrResponse(¶m, resolver, basePath); resolver.shouldStopOnError(err) {
495 op.Parameters[i] = param
498 if op.Responses != nil {
499 responses := op.Responses
500 if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {
503 for code := range responses.StatusCodeResponses {
504 response := responses.StatusCodeResponses[code]
505 if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
508 responses.StatusCodeResponses[code] = response
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),
519 ContinueOnError: false,
520 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
521 AbsoluteCircularRef: true,
523 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
528 return expandParameterOrResponse(response, resolver, opts.RelativeBase)
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
537 specBasePath, _ = absPath(basePath)
539 opts := &ExpandOptions{
540 RelativeBase: specBasePath,
542 resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
547 return expandParameterOrResponse(response, resolver, opts.RelativeBase)
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),
555 ContinueOnError: false,
556 // when no base path is specified, remaining $ref (circular) are rendered with an absolute path
557 AbsoluteCircularRef: true,
559 resolver, err := defaultSchemaLoader(root, opts, nil, nil)
564 return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
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
573 specBasePath, _ = absPath(basePath)
575 opts := &ExpandOptions{
576 RelativeBase: specBasePath,
578 resolver, err := defaultSchemaLoader(nil, opts, nil, nil)
583 return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
586 func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
589 switch refable := input.(type) {
603 return nil, nil, fmt.Errorf("expand: unsupported type %T. Input should be of type *Parameter or *Response", input)
608 func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
609 ref, _, err := getRefAndSchema(input)
616 parentRefs := []string{}
617 if err := resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
620 ref, sch, _ := getRefAndSchema(input)
621 if ref.String() != "" {
622 transitiveResolver, err := resolver.transitiveResolver(basePath, *ref)
623 if transitiveResolver.shouldStopOnError(err) {
626 basePath = resolver.updateBasePath(transitiveResolver, basePath)
627 resolver = transitiveResolver
630 if sch != nil && sch.Ref.String() != "" {
631 // schema expanded to a $ref in another root
633 sch.Ref, ern = NewRef(normalizePaths(sch.Ref.String(), ref.RemoteURI()))
642 if !resolver.options.SkipSchemas && sch != nil {
643 s, err := expandSchema(*sch, parentRefs, resolver, basePath)
644 if resolver.shouldStopOnError(err) {