Skip to content

Commit 2ac6464

Browse files
committed
Add support to walker for walking references inside components
1 parent b32f84d commit 2ac6464

File tree

2 files changed

+169
-5
lines changed

2 files changed

+169
-5
lines changed

src/Microsoft.OpenApi/Services/OpenApiWalker.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,13 @@ internal void Walk(OpenApiCallback callback)
339339
return;
340340
}
341341

342+
var isAComponent = false; // Handle $refs within component
343+
if (_inComponents)
344+
{
345+
isAComponent = true;
346+
ExitComponents();
347+
}
348+
342349
_visitor.Visit(callback);
343350

344351
if (callback != null)
@@ -351,6 +358,11 @@ internal void Walk(OpenApiCallback callback)
351358
_visitor.CurrentKeys.Callback = null;
352359
}
353360
}
361+
362+
if (isAComponent)
363+
{
364+
EnterComponents();
365+
}
354366
}
355367

356368
/// <summary>
@@ -548,12 +560,24 @@ internal void Walk(OpenApiParameter parameter)
548560
return;
549561
}
550562

563+
var isAComponent = false; // Handle $refs within component
564+
if (_inComponents)
565+
{
566+
isAComponent = true;
567+
ExitComponents();
568+
}
569+
551570
_visitor.Visit(parameter);
552571
Walk(OpenApiConstants.Schema, () => Walk(parameter.Schema));
553572
Walk(OpenApiConstants.Content, () => Walk(parameter.Content));
554573
Walk(OpenApiConstants.Examples, () => Walk(parameter.Examples));
555574

556575
Walk(parameter as IOpenApiExtensible);
576+
577+
if (isAComponent)
578+
{
579+
EnterComponents();
580+
}
557581
}
558582

559583
/// <summary>
@@ -590,12 +614,23 @@ internal void Walk(OpenApiResponse response)
590614
return;
591615
}
592616

617+
var isAComponent = false; // Handle $refs within component
618+
if (_inComponents)
619+
{
620+
isAComponent = true;
621+
ExitComponents();
622+
}
623+
593624
_visitor.Visit(response);
594625
Walk(OpenApiConstants.Content, () => Walk(response.Content));
595626
Walk(OpenApiConstants.Links, () => Walk(response.Links));
596627
Walk(OpenApiConstants.Headers, () => Walk(response.Headers));
597-
598628
Walk(response as IOpenApiExtensible);
629+
630+
if (isAComponent)
631+
{
632+
EnterComponents();
633+
}
599634
}
600635

601636
/// <summary>
@@ -607,6 +642,12 @@ internal void Walk(OpenApiRequestBody requestBody)
607642
{
608643
return;
609644
}
645+
var isAComponent = false; // Handle $refs within component
646+
if (_inComponents)
647+
{
648+
isAComponent = true;
649+
ExitComponents();
650+
}
610651

611652
_visitor.Visit(requestBody);
612653

@@ -618,6 +659,11 @@ internal void Walk(OpenApiRequestBody requestBody)
618659
}
619660
}
620661
Walk(requestBody as IOpenApiExtensible);
662+
663+
if (isAComponent)
664+
{
665+
EnterComponents();
666+
}
621667
}
622668

623669
/// <summary>
@@ -750,6 +796,12 @@ internal void Walk(OpenApiSchema schema)
750796
{
751797
return;
752798
}
799+
var isAComponent = false; // Handle $refs within component
800+
if (_inComponents)
801+
{
802+
isAComponent = true;
803+
ExitComponents();
804+
}
753805

754806
if (_schemaLoop.Contains(schema))
755807
{
@@ -772,7 +824,7 @@ internal void Walk(OpenApiSchema schema)
772824

773825
if (schema.AnyOf != null)
774826
{
775-
Walk("anyOf", () => Walk(schema.AllOf));
827+
Walk("anyOf", () => Walk(schema.AnyOf));
776828
}
777829

778830
if (schema.Properties != null) {
@@ -790,6 +842,11 @@ internal void Walk(OpenApiSchema schema)
790842
Walk(schema as IOpenApiExtensible);
791843

792844
_schemaLoop.Pop();
845+
846+
if (isAComponent)
847+
{
848+
EnterComponents();
849+
}
793850
}
794851

795852
/// <summary>
@@ -958,13 +1015,24 @@ internal void Walk(OpenApiHeader header)
9581015
{
9591016
return;
9601017
}
1018+
var isAComponent = false; // Handle $refs within component
1019+
if (_inComponents)
1020+
{
1021+
isAComponent = true;
1022+
ExitComponents();
1023+
}
9611024

9621025
_visitor.Visit(header);
9631026
Walk(OpenApiConstants.Content, () => Walk(header.Content));
9641027
Walk(OpenApiConstants.Example, () => Walk(header.Example));
9651028
Walk(OpenApiConstants.Examples, () => Walk(header.Examples));
9661029
Walk(OpenApiConstants.Schema, () => Walk(header.Schema));
9671030
Walk(header as IOpenApiExtensible);
1031+
1032+
if (isAComponent)
1033+
{
1034+
EnterComponents();
1035+
}
9681036
}
9691037

9701038
/// <summary>

test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Linq;
56
using FluentAssertions;
7+
using Microsoft.OpenApi.Interfaces;
68
using Microsoft.OpenApi.Models;
79
using Microsoft.OpenApi.Services;
810
using Xunit;
@@ -16,7 +18,7 @@ public class WalkerLocationTests
1618
public void LocateTopLevelObjects()
1719
{
1820
var doc = new OpenApiDocument();
19-
21+
2022
var locator = new LocatorVisitor();
2123
var walker = new OpenApiWalker(locator);
2224
walker.Walk(doc);
@@ -108,7 +110,7 @@ public void LocatePathOperationContentSchema()
108110

109111
});
110112

111-
locator.Keys.ShouldBeEquivalentTo(new List<string> { "/test","Get","200", "application/json" });
113+
locator.Keys.ShouldBeEquivalentTo(new List<string> { "/test", "Get", "200", "application/json" });
112114
}
113115

114116
[Fact]
@@ -117,7 +119,7 @@ public void WalkDOMWithCycles()
117119
var loopySchema = new OpenApiSchema()
118120
{
119121
Type = "object",
120-
Properties = new Dictionary<string,OpenApiSchema>()
122+
Properties = new Dictionary<string, OpenApiSchema>()
121123
{
122124
["name"] = new OpenApiSchema() { Type = "string" }
123125
}
@@ -150,6 +152,96 @@ public void WalkDOMWithCycles()
150152
"#/tags"
151153
});
152154
}
155+
156+
/// <summary>
157+
/// Walk document and discover all references to components, including those inside components
158+
/// </summary>
159+
[Fact]
160+
public void LocateReferences()
161+
{
162+
163+
var baseSchema = new OpenApiSchema() {
164+
Reference = new OpenApiReference() {
165+
Id = "base",
166+
Type = ReferenceType.Schema
167+
},
168+
UnresolvedReference = false
169+
};
170+
171+
var derivedSchema = new OpenApiSchema
172+
{
173+
AnyOf = new List<OpenApiSchema>() { baseSchema },
174+
Reference = new OpenApiReference()
175+
{
176+
Id = "derived",
177+
Type = ReferenceType.Schema
178+
},
179+
UnresolvedReference = false
180+
};
181+
182+
var testHeader = new OpenApiHeader() {
183+
Schema = derivedSchema,
184+
Reference = new OpenApiReference()
185+
{
186+
Id = "test-header",
187+
Type = ReferenceType.Header
188+
},
189+
UnresolvedReference = false
190+
};
191+
192+
var doc = new OpenApiDocument
193+
{
194+
Paths = new OpenApiPaths() {
195+
{ "/", new OpenApiPathItem() {
196+
Operations = new Dictionary<OperationType, OpenApiOperation>() {
197+
{ OperationType.Get, new OpenApiOperation() {
198+
Responses = new OpenApiResponses()
199+
{
200+
{ "200",new OpenApiResponse() {
201+
Content = new Dictionary<string, OpenApiMediaType>(){
202+
{ "application/json", new OpenApiMediaType() {
203+
Schema = derivedSchema
204+
}
205+
}
206+
},
207+
Headers = new Dictionary<string, OpenApiHeader>()
208+
{
209+
{ "test-header",testHeader}
210+
}
211+
}
212+
}
213+
}
214+
}
215+
}
216+
}
217+
}
218+
}
219+
},
220+
Components = new OpenApiComponents()
221+
{
222+
Schemas = new Dictionary<string, OpenApiSchema>() {
223+
{ "derived", derivedSchema },
224+
{ "base", baseSchema },
225+
},
226+
Headers = new Dictionary<string, OpenApiHeader>()
227+
{
228+
{ "test-header", testHeader }
229+
}
230+
231+
}
232+
};
233+
234+
var locator = new LocatorVisitor();
235+
var walker = new OpenApiWalker(locator);
236+
walker.Walk(doc);
237+
238+
locator.Locations.Where(l=>l.StartsWith("referenceAt:")).ShouldBeEquivalentTo(new List<string> {
239+
"referenceAt: #/paths/~1/get/responses/200/content/application~1json/schema",
240+
"referenceAt: #/components/schemas/derived/anyOf/0",
241+
"referenceAt: #/paths/~1/get/responses/200/headers/test-header",
242+
"referenceAt: #/components/headers/test-header/schema"
243+
});
244+
}
153245
}
154246

155247
internal class LocatorVisitor : OpenApiVisitorBase
@@ -199,6 +291,10 @@ public override void Visit(OpenApiResponse response)
199291
Locations.Add(this.PathString);
200292
}
201293

294+
public override void Visit(IOpenApiReferenceable referenceable)
295+
{
296+
Locations.Add("referenceAt: " + this.PathString);
297+
}
202298
public override void Visit(IDictionary<string,OpenApiMediaType> content)
203299
{
204300
Locations.Add(this.PathString);

0 commit comments

Comments
 (0)