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 spec
16
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 {
32	RelativeBase        string                                // the path to the root document to expand. This is a file, not a directory
33	SkipSchemas         bool                                  // do not expand schemas, just paths, parameters and responses
34	ContinueOnError     bool                                  // continue expanding even after and error is found
35	PathLoader          func(string) (json.RawMessage, error) `json:"-"` // the document loading method that takes a path as input and yields a json document
36	AbsoluteCircularRef bool                                  // circular $ref remaining after expansion remain absolute URLs
37}
38
39func optionsOrDefault(opts *ExpandOptions) *ExpandOptions {
40	if opts != nil {
41		clone := *opts // shallow clone to avoid internal changes to be propagated to the caller
42		if clone.RelativeBase != "" {
43			clone.RelativeBase = normalizeBase(clone.RelativeBase)
44		}
45		// if the relative base is empty, let the schema loader choose a pseudo root document
46		return &clone
47	}
48	return &ExpandOptions{}
49}
50
51// ExpandSpec expands the references in a swagger spec
52func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
53	options = optionsOrDefault(options)
54	resolver := defaultSchemaLoader(spec, options, nil, nil)
55
56	specBasePath := options.RelativeBase
57
58	if !options.SkipSchemas {
59		for key, definition := range spec.Definitions {
60			parentRefs := make([]string, 0, 10)
61			parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key))
62
63			def, err := expandSchema(definition, parentRefs, resolver, specBasePath)
64			if resolver.shouldStopOnError(err) {
65				return err
66			}
67			if def != nil {
68				spec.Definitions[key] = *def
69			}
70		}
71	}
72
73	for key := range spec.Parameters {
74		parameter := spec.Parameters[key]
75		if err := expandParameterOrResponse(&parameter, resolver, specBasePath); resolver.shouldStopOnError(err) {
76			return err
77		}
78		spec.Parameters[key] = parameter
79	}
80
81	for key := range spec.Responses {
82		response := spec.Responses[key]
83		if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
84			return err
85		}
86		spec.Responses[key] = response
87	}
88
89	if spec.Paths != nil {
90		for key := range spec.Paths.Paths {
91			pth := spec.Paths.Paths[key]
92			if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) {
93				return err
94			}
95			spec.Paths.Paths[key] = pth
96		}
97	}
98
99	return nil
100}
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 {
109	if root == nil {
110		return ""
111	}
112
113	// cache the root document to resolve $ref's
114	normalizedBase := normalizeBase(rootBase)
115	cache.Set(normalizedBase, root)
116
117	return normalizedBase
118}
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 {
129	cache = cacheOrDefault(cache)
130	if root == nil {
131		root = schema
132	}
133
134	opts := &ExpandOptions{
135		// when a root is specified, cache the root as an in-memory document for $ref retrieval
136		RelativeBase:    baseForRoot(root, cache),
137		SkipSchemas:     false,
138		ContinueOnError: false,
139	}
140
141	return 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 {
148	if schema == nil {
149		return nil
150	}
151
152	cache = cacheOrDefault(cache)
153
154	opts = optionsOrDefault(opts)
155
156	resolver := defaultSchemaLoader(nil, opts, cache, nil)
157
158	parentRefs := make([]string, 0, 10)
159	s, err := expandSchema(*schema, parentRefs, resolver, opts.RelativeBase)
160	if err != nil {
161		return err
162	}
163	if s != nil {
164		// guard for when continuing on error
165		*schema = *s
166	}
167
168	return nil
169}
170
171func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
172	if target.Items == nil {
173		return &target, nil
174	}
175
176	// array
177	if target.Items.Schema != nil {
178		t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
179		if err != nil {
180			return nil, err
181		}
182		*target.Items.Schema = *t
183	}
184
185	// tuple
186	for i := range target.Items.Schemas {
187		t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
188		if err != nil {
189			return nil, err
190		}
191		target.Items.Schemas[i] = *t
192	}
193
194	return &target, nil
195}
196
197func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
198	if target.Ref.String() == "" && target.Ref.IsRoot() {
199		newRef := normalizeRef(&target.Ref, basePath)
200		target.Ref = *newRef
201		return &target, nil
202	}
203
204	// change the base path of resolution when an ID is encountered
205	// otherwise the basePath should inherit the parent's
206	if target.ID != "" {
207		basePath, _ = resolver.setSchemaID(target, target.ID, basePath)
208	}
209
210	if target.Ref.String() != "" {
211		return expandSchemaRef(target, parentRefs, resolver, basePath)
212	}
213
214	for k := range target.Definitions {
215		tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
216		if resolver.shouldStopOnError(err) {
217			return &target, err
218		}
219		if tt != nil {
220			target.Definitions[k] = *tt
221		}
222	}
223
224	t, err := expandItems(target, parentRefs, resolver, basePath)
225	if resolver.shouldStopOnError(err) {
226		return &target, err
227	}
228	if t != nil {
229		target = *t
230	}
231
232	for i := range target.AllOf {
233		t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
234		if resolver.shouldStopOnError(err) {
235			return &target, err
236		}
237		if t != nil {
238			target.AllOf[i] = *t
239		}
240	}
241
242	for i := range target.AnyOf {
243		t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
244		if resolver.shouldStopOnError(err) {
245			return &target, err
246		}
247		if t != nil {
248			target.AnyOf[i] = *t
249		}
250	}
251
252	for i := range target.OneOf {
253		t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
254		if resolver.shouldStopOnError(err) {
255			return &target, err
256		}
257		if t != nil {
258			target.OneOf[i] = *t
259		}
260	}
261
262	if target.Not != nil {
263		t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
264		if resolver.shouldStopOnError(err) {
265			return &target, err
266		}
267		if t != nil {
268			*target.Not = *t
269		}
270	}
271
272	for k := range target.Properties {
273		t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
274		if resolver.shouldStopOnError(err) {
275			return &target, err
276		}
277		if t != nil {
278			target.Properties[k] = *t
279		}
280	}
281
282	if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
283		t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
284		if resolver.shouldStopOnError(err) {
285			return &target, err
286		}
287		if t != nil {
288			*target.AdditionalProperties.Schema = *t
289		}
290	}
291
292	for k := range target.PatternProperties {
293		t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
294		if resolver.shouldStopOnError(err) {
295			return &target, err
296		}
297		if t != nil {
298			target.PatternProperties[k] = *t
299		}
300	}
301
302	for k := range target.Dependencies {
303		if target.Dependencies[k].Schema != nil {
304			t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
305			if resolver.shouldStopOnError(err) {
306				return &target, err
307			}
308			if t != nil {
309				*target.Dependencies[k].Schema = *t
310			}
311		}
312	}
313
314	if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
315		t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
316		if resolver.shouldStopOnError(err) {
317			return &target, err
318		}
319		if t != nil {
320			*target.AdditionalItems.Schema = *t
321		}
322	}
323	return &target, nil
324}
325
326func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
327	// if a Ref is found, all sibling fields are skipped
328	// Ref also changes the resolution scope of children expandSchema
329
330	// here the resolution scope is changed because a $ref was encountered
331	normalizedRef := normalizeRef(&target.Ref, basePath)
332	normalizedBasePath := normalizedRef.RemoteURI()
333
334	if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
335		// this means there is a cycle in the recursion tree: return the Ref
336		// - 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 basePath
338		debugLog("short circuit circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",
339			basePath, normalizedBasePath, normalizedRef.String())
340		if !resolver.options.AbsoluteCircularRef {
341			target.Ref = denormalizeRef(normalizedRef, resolver.context.basePath, resolver.context.rootID)
342		} else {
343			target.Ref = *normalizedRef
344		}
345		return &target, nil
346	}
347
348	var t *Schema
349	err := resolver.Resolve(&target.Ref, &t, basePath)
350	if resolver.shouldStopOnError(err) {
351		return nil, err
352	}
353
354	if t == nil {
355		// guard for when continuing on error
356		return &target, nil
357	}
358
359	parentRefs = append(parentRefs, normalizedRef.String())
360	transitiveResolver := resolver.transitiveResolver(basePath, target.Ref)
361
362	basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
363
364	return expandSchema(*t, parentRefs, transitiveResolver, basePath)
365}
366
367func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
368	if pathItem == nil {
369		return nil
370	}
371
372	parentRefs := make([]string, 0, 10)
373	if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {
374		return err
375	}
376
377	if pathItem.Ref.String() != "" {
378		transitiveResolver := resolver.transitiveResolver(basePath, pathItem.Ref)
379		basePath = transitiveResolver.updateBasePath(resolver, basePath)
380		resolver = transitiveResolver
381	}
382
383	pathItem.Ref = Ref{}
384	for i := range pathItem.Parameters {
385		if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) {
386			return err
387		}
388	}
389
390	ops := []*Operation{
391		pathItem.Get,
392		pathItem.Head,
393		pathItem.Options,
394		pathItem.Put,
395		pathItem.Post,
396		pathItem.Patch,
397		pathItem.Delete,
398	}
399	for _, op := range ops {
400		if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {
401			return err
402		}
403	}
404
405	return nil
406}
407
408func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
409	if op == nil {
410		return nil
411	}
412
413	for i := range op.Parameters {
414		param := op.Parameters[i]
415		if err := expandParameterOrResponse(&param, resolver, basePath); resolver.shouldStopOnError(err) {
416			return err
417		}
418		op.Parameters[i] = param
419	}
420
421	if op.Responses == nil {
422		return nil
423	}
424
425	responses := op.Responses
426	if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {
427		return err
428	}
429
430	for code := range responses.StatusCodeResponses {
431		response := responses.StatusCodeResponses[code]
432		if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
433			return err
434		}
435		responses.StatusCodeResponses[code] = response
436	}
437
438	return nil
439}
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 {
448	cache = cacheOrDefault(cache)
449	opts := &ExpandOptions{
450		RelativeBase: baseForRoot(root, cache),
451	}
452	resolver := defaultSchemaLoader(root, opts, cache, nil)
453
454	return 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 {
461	opts := optionsOrDefault(&ExpandOptions{
462		RelativeBase: basePath,
463	})
464	resolver := defaultSchemaLoader(nil, opts, nil, nil)
465
466	return 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 {
474	cache = cacheOrDefault(cache)
475
476	opts := &ExpandOptions{
477		RelativeBase: baseForRoot(root, cache),
478	}
479	resolver := defaultSchemaLoader(root, opts, cache, nil)
480
481	return 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 {
488	opts := optionsOrDefault(&ExpandOptions{
489		RelativeBase: basePath,
490	})
491	resolver := defaultSchemaLoader(nil, opts, nil, nil)
492
493	return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
494}
495
496func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
497	var (
498		ref *Ref
499		sch *Schema
500	)
501
502	switch refable := input.(type) {
503	case *Parameter:
504		if refable == nil {
505			return nil, nil, nil
506		}
507		ref = &refable.Ref
508		sch = refable.Schema
509	case *Response:
510		if refable == nil {
511			return nil, nil, nil
512		}
513		ref = &refable.Ref
514		sch = refable.Schema
515	default:
516		return nil, nil, fmt.Errorf("unsupported type: %T: %w", input, ErrExpandUnsupportedType)
517	}
518
519	return ref, sch, nil
520}
521
522func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
523	ref, _, err := getRefAndSchema(input)
524	if err != nil {
525		return err
526	}
527
528	if ref == nil {
529		return nil
530	}
531
532	parentRefs := make([]string, 0, 10)
533	if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
534		return err
535	}
536
537	ref, sch, _ := getRefAndSchema(input)
538	if ref.String() != "" {
539		transitiveResolver := resolver.transitiveResolver(basePath, *ref)
540		basePath = resolver.updateBasePath(transitiveResolver, basePath)
541		resolver = transitiveResolver
542	}
543
544	if sch == nil {
545		// nothing to be expanded
546		if ref != nil {
547			*ref = Ref{}
548		}
549		return nil
550	}
551
552	if sch.Ref.String() != "" {
553		rebasedRef, ern := NewRef(normalizeURI(sch.Ref.String(), basePath))
554		if ern != nil {
555			return ern
556		}
557
558		switch {
559		case resolver.isCircular(&rebasedRef, basePath, parentRefs...):
560			// this is a circular $ref: stop expansion
561			if !resolver.options.AbsoluteCircularRef {
562				sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
563			} else {
564				sch.Ref = rebasedRef
565			}
566		case !resolver.options.SkipSchemas:
567			// schema expanded to a $ref in another root
568			sch.Ref = rebasedRef
569			debugLog("rebased to: %s", sch.Ref.String())
570		default:
571			// skip schema expansion but rebase $ref to schema
572			sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
573		}
574	}
575
576	if ref != nil {
577		*ref = Ref{}
578	}
579
580	// expand schema
581	if !resolver.options.SkipSchemas {
582		s, err := expandSchema(*sch, parentRefs, resolver, basePath)
583		if resolver.shouldStopOnError(err) {
584			return err
585		}
586		if s == nil {
587			// guard for when continuing on error
588			return nil
589		}
590		*sch = *s
591	}
592
593	return nil
594}
595