Skip to content

Commit 65b70f2

Browse files
committed
Enabled async read for workspace
1 parent d229688 commit 65b70f2

File tree

9 files changed

+179
-22
lines changed

9 files changed

+179
-22
lines changed

src/Microsoft.OpenApi.Readers/Interface/IStreamLoader.cs

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

44
using System;
5+
using System.IO;
56
using System.Threading.Tasks;
67
using Microsoft.OpenApi.Models;
78

89
namespace Microsoft.OpenApi.Readers.Interface
910
{
1011
/// <summary>
11-
/// Interface for service that translates a URI into an object that can be loaded by a Reader
12+
/// Interface for service that translates a URI into a stream that can be loaded by a Reader
1213
/// </summary>
13-
/// <typeparam name="TInput"></typeparam>
14-
public interface IInputLoader<TInput>
14+
public interface IStreamLoader
1515
{
1616
/// <summary>
1717
/// Use Uri to locate data and convert into an input object.
1818
/// </summary>
1919
/// <param name="uri">Identifier of some source of an OpenAPI Description</param>
2020
/// <returns>A data objext that can be processed by a reader to generate an <see cref="OpenApiDocument"/></returns>
21-
Task<TInput> LoadAsync(Uri uri);
21+
Task<Stream> LoadAsync(Uri uri);
2222

2323
/// <summary>
2424
/// Use Uri to locate data and convert into an input object.
2525
/// </summary>
2626
/// <param name="uri"></param>
2727
/// <returns></returns>
28-
TInput Load(Uri uri);
28+
Stream Load(Uri uri);
2929
}
3030
}

src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ public class OpenApiReaderSettings
6666
/// <remarks>
6767
/// Default loader will attempt to dereference http(s) urls and file urls.
6868
/// </remarks>
69-
public Func<Uri, Stream> CustomExternalLoader { get; set; }
69+
public IStreamLoader CustomExternalLoader { get; set; }
7070
}
7171
}

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

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

44
using System.IO;
5+
using System.Threading.Tasks;
56
using Microsoft.OpenApi.Interfaces;
67
using Microsoft.OpenApi.Models;
78
using Microsoft.OpenApi.Readers.Interface;
@@ -38,6 +39,31 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
3839
}
3940
}
4041

42+
/// <summary>
43+
/// Reads the stream input and parses it into an Open API document.
44+
/// </summary>
45+
/// <param name="input">Stream containing OpenAPI description to parse.</param>
46+
/// <returns>Instance result containing newly created OpenApiDocument and diagnostics object from the process</returns>
47+
public async Task<ReadResult> ReadAsync(Stream input)
48+
{
49+
MemoryStream bufferedStream;
50+
if (input is MemoryStream)
51+
{
52+
bufferedStream = (MemoryStream)input;
53+
}
54+
else
55+
{
56+
// Buffer stream so that OpenApiTextReaderReader can process it synchronously
57+
// YamlDocument doesn't support async reading.
58+
bufferedStream = new MemoryStream();
59+
await input.CopyToAsync(bufferedStream);
60+
}
61+
62+
var reader = new StreamReader(bufferedStream);
63+
64+
return await new OpenApiTextReaderReader(_settings).ReadAsync(reader);
65+
}
66+
4167
/// <summary>
4268
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
4369
/// </summary>

src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.IO;
55
using System.Linq;
6+
using System.Threading.Tasks;
67
using Microsoft.OpenApi.Interfaces;
78
using Microsoft.OpenApi.Models;
89
using Microsoft.OpenApi.Readers.Interface;
@@ -51,6 +52,31 @@ public OpenApiDocument Read(TextReader input, out OpenApiDiagnostic diagnostic)
5152

5253
return new OpenApiYamlDocumentReader(this._settings).Read(yamlDocument, out diagnostic);
5354
}
55+
56+
public async Task<ReadResult> ReadAsync(TextReader input)
57+
{
58+
YamlDocument yamlDocument;
59+
60+
// Parse the YAML/JSON text in the TextReader into the YamlDocument
61+
try
62+
{
63+
yamlDocument = LoadYamlDocument(input);
64+
}
65+
catch (YamlException ex)
66+
{
67+
var diagnostic = new OpenApiDiagnostic();
68+
diagnostic.Errors.Add(new OpenApiError($"#line={ex.Start.Line}", ex.Message));
69+
return new ReadResult
70+
{
71+
OpenApiDocument = null,
72+
OpenApiDiagnostic = diagnostic
73+
};
74+
}
75+
76+
return await new OpenApiYamlDocumentReader(this._settings).ReadAsync(yamlDocument);
77+
}
78+
79+
5480
/// <summary>
5581
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
5682
/// </summary>

src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.IO;
6+
using System.Threading.Tasks;
67
using Microsoft.OpenApi.Exceptions;
78
using Microsoft.OpenApi.Extensions;
89
using Microsoft.OpenApi.Interfaces;
@@ -71,7 +72,67 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic
7172
return document;
7273
}
7374

75+
public async Task<ReadResult> ReadAsync(YamlDocument input)
76+
{
77+
var diagnostic = new OpenApiDiagnostic();
78+
var context = new ParsingContext(diagnostic)
79+
{
80+
ExtensionParsers = _settings.ExtensionParsers,
81+
BaseUrl = _settings.BaseUrl
82+
};
83+
84+
OpenApiDocument document = null;
85+
try
86+
{
87+
// Parse the OpenAPI Document
88+
document = context.Parse(input);
89+
90+
await ResolveReferencesAsync(diagnostic, document);
91+
}
92+
catch (OpenApiException ex)
93+
{
94+
diagnostic.Errors.Add(new OpenApiError(ex));
95+
}
96+
97+
// Validate the document
98+
if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0)
99+
{
100+
var errors = document.Validate(_settings.RuleSet);
101+
foreach (var item in errors)
102+
{
103+
diagnostic.Errors.Add(item);
104+
}
105+
}
106+
107+
return new ReadResult()
108+
{
109+
OpenApiDocument = document,
110+
OpenApiDiagnostic = diagnostic
111+
};
112+
}
113+
114+
74115
private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document)
116+
{
117+
// Resolve References if requested
118+
switch (_settings.ReferenceResolution)
119+
{
120+
case ReferenceResolutionSetting.ResolveAllReferences:
121+
throw new ArgumentException("Cannot resolve all references via a synchronous call. Use ReadAsync.");
122+
case ReferenceResolutionSetting.ResolveLocalReferences:
123+
var errors = document.ResolveReferences(false);
124+
125+
foreach (var item in errors)
126+
{
127+
diagnostic.Errors.Add(item);
128+
}
129+
break;
130+
case ReferenceResolutionSetting.DoNotResolveReferences:
131+
break;
132+
}
133+
}
134+
135+
private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document)
75136
{
76137
// Resolve References if requested
77138
switch (_settings.ReferenceResolution)
@@ -81,8 +142,8 @@ private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument doc
81142
document.Workspace = openApiWorkSpace;
82143
var streamLoader = new DefaultStreamLoader();
83144

84-
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader.Load, _settings);
85-
workspaceLoader.Load(new OpenApiReference() { ExternalResource = "/" }, document);
145+
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
146+
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);
86147
break;
87148
case ReferenceResolutionSetting.ResolveLocalReferences:
88149
var errors = document.ResolveReferences(false);
@@ -97,6 +158,7 @@ private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument doc
97158
}
98159
}
99160

161+
100162
/// <summary>
101163
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
102164
/// </summary>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.OpenApi.Models;
7+
8+
namespace Microsoft.OpenApi.Readers
9+
{
10+
/// <summary>
11+
///
12+
/// </summary>
13+
public class ReadResult
14+
{
15+
public OpenApiDocument OpenApiDocument { set; get; }
16+
public OpenApiDiagnostic OpenApiDiagnostic { set; get; }
17+
}
18+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.OpenApi.Readers.Services
1515
/// <summary>
1616
/// Implementation of IInputLoader that loads streams from URIs
1717
/// </summary>
18-
internal class DefaultStreamLoader : IInputLoader<Stream>
18+
internal class DefaultStreamLoader : IStreamLoader
1919
{
2020
private HttpClient _httpClient = new HttpClient();
2121

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ namespace Microsoft.OpenApi.Readers.Services
1414
internal class OpenApiWorkspaceLoader
1515
{
1616
private OpenApiWorkspace _workspace;
17-
private Func<Uri, Stream> _loader;
17+
private IStreamLoader _loader;
1818
private OpenApiDiagnostic _diagnostics;
1919
private OpenApiReaderSettings _readerSettings;
2020

21-
public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, Func<Uri,Stream> loader, OpenApiReaderSettings readerSettings)
21+
public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, IStreamLoader loader, OpenApiReaderSettings readerSettings)
2222
{
2323
_workspace = workspace;
2424
_loader = loader;
2525
_readerSettings = readerSettings;
2626
}
2727

28-
internal void Load(OpenApiReference reference, OpenApiDocument document)
28+
internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument document)
2929
{
3030
_workspace.AddDocument(reference.ExternalResource, document);
3131
document.Workspace = _workspace;
@@ -43,9 +43,9 @@ internal void Load(OpenApiReference reference, OpenApiDocument document)
4343
// If not already in workspace, load it and process references
4444
if (!_workspace.Contains(item.ExternalResource))
4545
{
46-
var input = _loader(new Uri(item.ExternalResource));
47-
var newDocument = reader.Read(input, out _diagnostics); // TODO merge _diagnositics
48-
Load(item, newDocument);
46+
var input = _loader.Load(new Uri(item.ExternalResource));
47+
var result = await reader.ReadAsync(input); // TODO merge _diagnositics
48+
await LoadAsync(item, result.OpenApiDocument);
4949
}
5050
}
5151
}

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

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,45 @@ public class OpenApiWorkspaceStreamTests
1818

1919
// Use OpenApiWorkspace to load a document and a referenced document
2020

21-
//[Fact]
22-
public void LoadDocumentIntoWorkspace()
21+
[Fact]
22+
public async Task LoadDocumentIntoWorkspace()
2323
{
2424
// Create a reader that will resolve all references
25-
var reader = new OpenApiStringReader(new OpenApiReaderSettings() {
26-
ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
27-
CustomExternalLoader = (url) => { return null; }
25+
var reader = new OpenApiStreamReader(new OpenApiReaderSettings()
26+
{
27+
ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
28+
CustomExternalLoader = new MockLoader()
2829
});
2930

3031
// Todo: this should be ReadAsync
31-
var doc = reader.Read("", out OpenApiDiagnostic diagnostic);
32+
var stream = new MemoryStream();
33+
var doc = @"openapi: 3.0.0
34+
info:
35+
title: foo
36+
version: 1.0.0
37+
paths: {}";
38+
var wr = new StreamWriter(stream);
39+
wr.Write(doc);
40+
wr.Flush();
41+
stream.Position = 0;
3242

33-
Assert.NotNull(doc.Workspace);
43+
var result = await reader.ReadAsync(stream);
3444

45+
Assert.NotNull(result.OpenApiDocument.Workspace);
46+
47+
}
48+
}
49+
50+
public class MockLoader : IStreamLoader
51+
{
52+
public Stream Load(Uri uri)
53+
{
54+
return null;
55+
}
56+
57+
public async Task<Stream> LoadAsync(Uri uri)
58+
{
59+
return null;
3560
}
3661
}
3762
}

0 commit comments

Comments
 (0)