Skip to content

Commit 45968f3

Browse files
committed
Can validate external references now
1 parent 1374d77 commit 45968f3

File tree

8 files changed

+150
-50
lines changed

8 files changed

+150
-50
lines changed

src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public async Task<ReadResult> ReadAsync(YamlDocument input)
8888
// Parse the OpenAPI Document
8989
document = context.Parse(input);
9090

91-
await ResolveReferencesAsync(diagnostic, document);
91+
await ResolveReferencesAsync(diagnostic, document, _settings.BaseUrl);
9292
}
9393
catch (OpenApiException ex)
9494
{
@@ -133,7 +133,7 @@ private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument doc
133133
}
134134
}
135135

136-
private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document)
136+
private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document, Uri baseUrl)
137137
{
138138
List<OpenApiError> errors = new List<OpenApiError>();
139139

@@ -146,7 +146,7 @@ private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiD
146146
var openApiWorkSpace = new OpenApiWorkspace();
147147

148148
// Load this root document into the workspace
149-
var streamLoader = new DefaultStreamLoader();
149+
var streamLoader = new DefaultStreamLoader(baseUrl);
150150
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
151151
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);
152152

src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,41 @@ namespace Microsoft.OpenApi.Readers.Services
1414
/// </summary>
1515
internal class DefaultStreamLoader : IStreamLoader
1616
{
17+
private readonly Uri baseUrl;
1718
private HttpClient _httpClient = new HttpClient();
1819

20+
21+
public DefaultStreamLoader(Uri baseUrl)
22+
{
23+
this.baseUrl = baseUrl;
24+
}
25+
1926
public Stream Load(Uri uri)
2027
{
28+
var absoluteUri = new Uri(baseUrl, uri);
2129
switch (uri.Scheme)
2230
{
2331
case "file":
24-
return File.OpenRead(uri.AbsolutePath);
32+
return File.OpenRead(absoluteUri.AbsolutePath);
2533
case "http":
2634
case "https":
27-
return _httpClient.GetStreamAsync(uri).GetAwaiter().GetResult();
28-
35+
return _httpClient.GetStreamAsync(absoluteUri).GetAwaiter().GetResult();
2936
default:
3037
throw new ArgumentException("Unsupported scheme");
3138
}
3239
}
3340

3441
public async Task<Stream> LoadAsync(Uri uri)
3542
{
36-
switch (uri.Scheme)
43+
var absoluteUri = new Uri(baseUrl, uri);
44+
45+
switch (absoluteUri.Scheme)
3746
{
3847
case "file":
39-
return File.OpenRead(uri.AbsolutePath);
48+
return File.OpenRead(absoluteUri.AbsolutePath);
4049
case "http":
4150
case "https":
42-
return await _httpClient.GetStreamAsync(uri);
51+
return await _httpClient.GetStreamAsync(absoluteUri);
4352
default:
4453
throw new ArgumentException("Unsupported scheme");
4554
}

src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ internal class OpenApiV3VersionService : IOpenApiVersionService
5353
/// <summary>
5454
/// Parse the string to a <see cref="OpenApiReference"/> object.
5555
/// </summary>
56+
/// <param name="reference">The URL of the reference</param>
57+
/// <param name="type">The type of object refefenced based on the context of the reference</param>
5658
public OpenApiReference ConvertToOpenApiReference(
5759
string reference,
5860
ReferenceType? type)
@@ -95,8 +97,22 @@ public OpenApiReference ConvertToOpenApiReference(
9597
// $ref: externalSource.yaml#/Pet
9698
if (id.StartsWith("/components/"))
9799
{
98-
id = segments[1].Split('/')[3];
99-
}
100+
var localSegments = segments[1].Split('/');
101+
var referencedType = localSegments[2].GetEnumFromDisplayName<ReferenceType>();
102+
if (type == null)
103+
{
104+
type = referencedType;
105+
}
106+
else
107+
{
108+
if (type != referencedType)
109+
{
110+
throw new OpenApiException("Referenced type mismatch");
111+
}
112+
}
113+
id = localSegments[3];
114+
}
115+
100116
return new OpenApiReference
101117
{
102118
ExternalResource = segments[0],

src/Microsoft.OpenApi.Tool/OpenApiService.cs

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net;
66
using System.Net.Http;
77
using System.Text;
8+
using System.Threading.Tasks;
89
using Microsoft.OpenApi.Extensions;
910
using Microsoft.OpenApi.Models;
1011
using Microsoft.OpenApi.Readers;
@@ -29,14 +30,16 @@ public static void ProcessOpenApiDocument(
2930
throw new ArgumentNullException("input");
3031
}
3132

32-
var stream = GetStream(input);
33+
var inputUrl = GetInputUrl(input);
34+
var stream = GetStream(inputUrl);
3335

3436
OpenApiDocument document;
3537

3638
var result = new OpenApiStreamReader(new OpenApiReaderSettings
3739
{
3840
ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
39-
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
41+
RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
42+
BaseUrl = new Uri(inputUrl.AbsoluteUri)
4043
}
4144
).ReadAsync(stream).GetAwaiter().GetResult();
4245

@@ -91,10 +94,22 @@ public static void ProcessOpenApiDocument(
9194
}
9295
}
9396

94-
private static Stream GetStream(string input)
97+
private static Uri GetInputUrl(string input)
9598
{
96-
Stream stream;
9799
if (input.StartsWith("http"))
100+
{
101+
return new Uri(input);
102+
}
103+
else
104+
{
105+
return new Uri("file://" + Path.GetFullPath(input));
106+
}
107+
}
108+
109+
private static Stream GetStream(Uri input)
110+
{
111+
Stream stream;
112+
if (input.Scheme == "http" || input.Scheme == "https")
98113
{
99114
var httpClient = new HttpClient(new HttpClientHandler()
100115
{
@@ -105,32 +120,40 @@ private static Stream GetStream(string input)
105120
};
106121
stream = httpClient.GetStreamAsync(input).Result;
107122
}
108-
else
123+
else if (input.Scheme == "file")
109124
{
110-
var fileInput = new FileInfo(input);
125+
var fileInput = new FileInfo(input.AbsolutePath);
111126
stream = fileInput.OpenRead();
127+
}
128+
else
129+
{
130+
throw new ArgumentException("Unrecognized exception");
112131
}
113132

114133
return stream;
115134
}
116135

117-
internal static void ValidateOpenApiDocument(string input)
136+
internal static async Task ValidateOpenApiDocument(string input, bool resolveExternal)
118137
{
119138
if (input == null)
120139
{
121140
throw new ArgumentNullException("input");
122141
}
123-
124-
var stream = GetStream(input);
142+
var inputUrl = GetInputUrl(input);
143+
var stream = GetStream(GetInputUrl(input));
125144

126145
OpenApiDocument document;
127146

128-
document = new OpenApiStreamReader(new OpenApiReaderSettings
147+
var result = await new OpenApiStreamReader(new OpenApiReaderSettings
129148
{
130-
//ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
131-
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
149+
ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
150+
RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
151+
BaseUrl = new Uri(inputUrl.AbsoluteUri)
132152
}
133-
).Read(stream, out var context);
153+
).ReadAsync(stream);
154+
155+
document = result.OpenApiDocument;
156+
var context = result.OpenApiDiagnostic;
134157

135158
if (context.Errors.Count != 0)
136159
{
@@ -140,11 +163,25 @@ internal static void ValidateOpenApiDocument(string input)
140163
}
141164
}
142165

143-
var statsVisitor = new StatsVisitor();
144-
var walker = new OpenApiWalker(statsVisitor);
145-
walker.Walk(document);
166+
if (document.Workspace == null) {
167+
var statsVisitor = new StatsVisitor();
168+
var walker = new OpenApiWalker(statsVisitor);
169+
walker.Walk(document);
170+
Console.WriteLine(statsVisitor.GetStatisticsReport());
171+
}
172+
else
173+
{
174+
foreach (var memberDocument in document.Workspace.Documents)
175+
{
176+
Console.WriteLine("Stats for " + memberDocument.Info.Title);
177+
var statsVisitor = new StatsVisitor();
178+
var walker = new OpenApiWalker(statsVisitor);
179+
walker.Walk(memberDocument);
180+
Console.WriteLine(statsVisitor.GetStatisticsReport());
181+
}
182+
}
146183

147-
Console.WriteLine(statsVisitor.GetStatisticsReport());
184+
148185
}
149186
}
150187
}

src/Microsoft.OpenApi.Tool/Program.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ static async Task<int> Main(string[] args)
3636

3737
var validateCommand = new Command("validate")
3838
{
39-
new Option("--input", "Input OpenAPI description file path or URL", typeof(string) )
39+
new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ),
40+
new Option("--resolveExternal","Resolve external $refs", typeof(bool))
4041
};
41-
validateCommand.Handler = CommandHandler.Create<string>(OpenApiService.ValidateOpenApiDocument);
42+
validateCommand.Handler = CommandHandler.Create<string,bool>(OpenApiService.ValidateOpenApiDocument);
4243

4344
var transformCommand = new Command("transform")
4445
{

src/Microsoft.OpenApi/Models/OpenApiReference.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public string ReferenceV3
5454
{
5555
if (IsExternal)
5656
{
57-
return GetExternalReference();
57+
return GetExternalReferenceV3();
5858
}
5959

6060
if (!Type.HasValue)
@@ -85,7 +85,7 @@ public string ReferenceV2
8585
{
8686
if (IsExternal)
8787
{
88-
return GetExternalReference();
88+
return GetExternalReferenceV2();
8989
}
9090

9191
if (!Type.HasValue)
@@ -171,11 +171,21 @@ public void SerializeAsV2(IOpenApiWriter writer)
171171
writer.WriteEndObject();
172172
}
173173

174-
private string GetExternalReference()
174+
private string GetExternalReferenceV3()
175175
{
176176
if (Id != null)
177177
{
178-
return ExternalResource + "#/" + Id;
178+
return ExternalResource + "#/components/" + Type.GetDisplayName() + "/"+ Id;
179+
}
180+
181+
return ExternalResource;
182+
}
183+
184+
private string GetExternalReferenceV2()
185+
{
186+
if (Id != null)
187+
{
188+
return ExternalResource + "#/" + GetReferenceTypeNameAsV2((ReferenceType)Type) + "/" + Id;
179189
}
180190

181191
return ExternalResource;

test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class OpenApiWorkspaceStreamTests
1919
// Use OpenApiWorkspace to load a document and a referenced document
2020

2121
[Fact]
22-
public async Task LoadDocumentIntoWorkspace()
22+
public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoWorkspace()
2323
{
2424
// Create a reader that will resolve all references
2525
var reader = new OpenApiStreamReader(new OpenApiReaderSettings()
@@ -48,7 +48,7 @@ public async Task LoadDocumentIntoWorkspace()
4848

4949

5050
[Fact]
51-
public async Task LoadTodoDocumentIntoWorkspace()
51+
public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWorkspace()
5252
{
5353
// Create a reader that will resolve all references
5454
var reader = new OpenApiStreamReader(new OpenApiReaderSettings()
@@ -65,6 +65,7 @@ public async Task LoadTodoDocumentIntoWorkspace()
6565

6666
Assert.NotNull(result.OpenApiDocument.Workspace);
6767
Assert.True(result.OpenApiDocument.Workspace.Contains("TodoComponents.yaml"));
68+
6869
var referencedSchema = result.OpenApiDocument
6970
.Paths["/todos"]
7071
.Operations[OperationType.Get]

0 commit comments

Comments
 (0)