Skip to content

Commit ed00e71

Browse files
authored
fix(expand): parameters & responses should properly follow remote doc resolution when SKipSchema (#183)
* fix(expand): parameters & responses should properly follow remote doc resolution when SKipSchema * fixes #182 * contributes go-swagger/go-swagger#2743 Signed-off-by: Frederic BIDON <[email protected]> * fixed assertion in test for windows paths Signed-off-by: Frederic BIDON <[email protected]> --------- Signed-off-by: Frederic BIDON <[email protected]>
1 parent b445199 commit ed00e71

21 files changed

+225
-24
lines changed

expander.go

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,19 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
213213
}
214214

215215
if target.Ref.String() != "" {
216-
return expandSchemaRef(target, parentRefs, resolver, basePath)
216+
if !resolver.options.SkipSchemas {
217+
return expandSchemaRef(target, parentRefs, resolver, basePath)
218+
}
219+
220+
// when "expand" with SkipSchema, we just rebase the existing $ref without replacing
221+
// the full schema.
222+
rebasedRef, err := NewRef(normalizeURI(target.Ref.String(), basePath))
223+
if err != nil {
224+
return nil, err
225+
}
226+
target.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
227+
228+
return &target, nil
217229
}
218230

219231
for k := range target.Definitions {
@@ -525,21 +537,25 @@ func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
525537
}
526538

527539
func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
528-
ref, _, err := getRefAndSchema(input)
540+
ref, sch, err := getRefAndSchema(input)
529541
if err != nil {
530542
return err
531543
}
532544

533-
if ref == nil {
545+
if ref == nil && sch == nil { // nothing to do
534546
return nil
535547
}
536548

537549
parentRefs := make([]string, 0, 10)
538-
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
539-
return err
550+
if ref != nil {
551+
// dereference this $ref
552+
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
553+
return err
554+
}
555+
556+
ref, sch, _ = getRefAndSchema(input)
540557
}
541558

542-
ref, sch, _ := getRefAndSchema(input)
543559
if ref.String() != "" {
544560
transitiveResolver := resolver.transitiveResolver(basePath, *ref)
545561
basePath = resolver.updateBasePath(transitiveResolver, basePath)
@@ -551,6 +567,7 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa
551567
if ref != nil {
552568
*ref = Ref{}
553569
}
570+
554571
return nil
555572
}
556573

@@ -560,38 +577,29 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa
560577
return ern
561578
}
562579

563-
switch {
564-
case resolver.isCircular(&rebasedRef, basePath, parentRefs...):
580+
if resolver.isCircular(&rebasedRef, basePath, parentRefs...) {
565581
// this is a circular $ref: stop expansion
566582
if !resolver.options.AbsoluteCircularRef {
567583
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
568584
} else {
569585
sch.Ref = rebasedRef
570586
}
571-
case !resolver.options.SkipSchemas:
572-
// schema expanded to a $ref in another root
573-
sch.Ref = rebasedRef
574-
debugLog("rebased to: %s", sch.Ref.String())
575-
default:
576-
// skip schema expansion but rebase $ref to schema
577-
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
578587
}
579588
}
580589

590+
// $ref expansion or rebasing is performed by expandSchema below
581591
if ref != nil {
582592
*ref = Ref{}
583593
}
584594

585595
// expand schema
586-
if !resolver.options.SkipSchemas {
587-
s, err := expandSchema(*sch, parentRefs, resolver, basePath)
588-
if resolver.shouldStopOnError(err) {
589-
return err
590-
}
591-
if s == nil {
592-
// guard for when continuing on error
593-
return nil
594-
}
596+
// yes, we do it even if options.SkipSchema is true: we have to go down that rabbit hole and rebase nested $ref)
597+
s, err := expandSchema(*sch, parentRefs, resolver, basePath)
598+
if resolver.shouldStopOnError(err) {
599+
return err
600+
}
601+
602+
if s != nil { // guard for when continuing on error
595603
*sch = *s
596604
}
597605

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
swagger: '2.0'
2+
info:
3+
version: 0.0.0
4+
title: Simple API
5+
paths:
6+
/bar:
7+
$ref: 'swagger/paths/bar.yml'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
swagger: '2.0'
2+
info:
3+
version: 0.0.0
4+
title: Simple API
5+
paths:
6+
/foo:
7+
$ref: 'swagger/paths/foo.yml'
8+
/bar:
9+
$ref: 'swagger/paths/bar.yml'
10+
/nested:
11+
$ref: 'swagger/paths/nested.yml#/response'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ErrorPayload:
2+
title: Error Payload
3+
required:
4+
- errors
5+
properties:
6+
errors:
7+
type: array
8+
items:
9+
$ref: './items.yml#/ErrorDetailsItem'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ErrorDetailsItem:
2+
title: Error details item
3+
description: Represents an item of the list of details of an error.
4+
required:
5+
- message
6+
- code
7+
properties:
8+
message:
9+
type: string
10+
code:
11+
type: string
12+
details:
13+
type: array
14+
items:
15+
type: string
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
get:
2+
responses:
3+
200:
4+
description: OK
5+
schema:
6+
type: array
7+
items:
8+
$ref: '../user/index.yml#/User' ## this doesn't work
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
get:
2+
responses:
3+
200:
4+
description: OK
5+
500:
6+
description: OK
7+
schema:
8+
$ref: '../definitions.yml#/ErrorPayload'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/foo:
2+
$ref: ./foo.yml
3+
/bar:
4+
$ref: ./bar.yml
5+
/nested:
6+
$ref: ./nested.yml#/response
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
response:
2+
get:
3+
responses:
4+
200:
5+
description: OK
6+
schema:
7+
$ref: '#/definitions/SameFileReference'
8+
9+
definitions:
10+
SameFileReference:
11+
type: object
12+
properties:
13+
name:
14+
type: string
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
User:
2+
$ref: './model.yml'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type: object
2+
properties:
3+
name:
4+
type: string

fixtures/bugs/2743/working/spec.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
swagger: '2.0'
2+
info:
3+
version: 0.0.0
4+
title: Simple API
5+
paths:
6+
/foo:
7+
$ref: 'swagger/paths/foo.yml'
8+
/bar:
9+
$ref: 'swagger/paths/bar.yml'
10+
/nested:
11+
$ref: 'swagger/paths/nested.yml#/response'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ErrorPayload:
2+
title: Error Payload
3+
required:
4+
- errors
5+
properties:
6+
errors:
7+
type: array
8+
items:
9+
$ref: './items.yml#/ErrorDetailsItem'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ErrorDetailsItem:
2+
title: Error details item
3+
description: Represents an item of the list of details of an error.
4+
required:
5+
- message
6+
- code
7+
properties:
8+
message:
9+
type: string
10+
code:
11+
type: string
12+
details:
13+
type: array
14+
items:
15+
type: string
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
get:
2+
responses:
3+
200:
4+
description: OK
5+
schema:
6+
type: array
7+
items:
8+
$ref: './swagger/user/index.yml#/User' ## this works
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
get:
2+
responses:
3+
200:
4+
description: OK
5+
500:
6+
description: OK
7+
schema:
8+
$ref: '../definitions.yml#/ErrorPayload'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/foo:
2+
$ref: ./foo.yml
3+
/bar:
4+
$ref: ./bar.yml
5+
/nested:
6+
$ref: ./nested.yml#/response
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
response:
2+
get:
3+
responses:
4+
200:
5+
description: OK
6+
schema:
7+
$ref: '#/definitions/SameFileReference'
8+
9+
definitions:
10+
SameFileReference:
11+
type: object
12+
properties:
13+
name:
14+
type: string
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
User:
2+
$ref: './model.yml'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type: object
2+
properties:
3+
name:
4+
type: string

spec_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,38 @@ import (
2525
)
2626

2727
// Test unitary fixture for dev and bug fixing
28+
29+
func TestSpec_Issue2743(t *testing.T) {
30+
t.Run("should expand but produce unresolvable $ref", func(t *testing.T) {
31+
path := filepath.Join("fixtures", "bugs", "2743", "working", "spec.yaml")
32+
sp := loadOrFail(t, path)
33+
require.NoError(t,
34+
spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}),
35+
)
36+
37+
t.Run("all $ref do not resolve when expanding again", func(t *testing.T) {
38+
err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader})
39+
require.Error(t, err)
40+
require.ErrorContains(t, err, filepath.FromSlash("swagger/paths/swagger/user/index.yml"))
41+
})
42+
})
43+
44+
t.Run("should expand and produce resolvable $ref", func(t *testing.T) {
45+
path := filepath.Join("fixtures", "bugs", "2743", "not-working", "spec.yaml")
46+
sp := loadOrFail(t, path)
47+
require.NoError(t,
48+
spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}),
49+
)
50+
51+
t.Run("all $ref properly reolve when expanding again", func(t *testing.T) {
52+
require.NoError(t,
53+
spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}),
54+
)
55+
require.NotContainsf(t, asJSON(t, sp), "$ref", "all $ref's should have been expanded properly")
56+
})
57+
})
58+
}
59+
2860
func TestSpec_Issue1429(t *testing.T) {
2961
path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")
3062

0 commit comments

Comments
 (0)