Skip to content

Commit 60ef50e

Browse files
committed
Workspace reference
1 parent b332d86 commit 60ef50e

File tree

9 files changed

+255
-35
lines changed

9 files changed

+255
-35
lines changed
Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Text;
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
65
using System.Threading.Tasks;
6+
using Microsoft.OpenApi.Models;
77

88
namespace Microsoft.OpenApi.Readers.Interface
99
{
10-
public interface IStreamLoader
10+
/// <summary>
11+
/// Interface for service that translates a URI into an object that can be loaded by a Reader
12+
/// </summary>
13+
/// <typeparam name="TInput"></typeparam>
14+
public interface IInputLoader<TInput>
1115
{
12-
Task<Stream> LoadAsync(Uri uri);
16+
/// <summary>
17+
/// Use Uri to locate data and convert into an input object.
18+
/// </summary>
19+
/// <param name="uri">Identifier of some source of an OpenAPI Description</param>
20+
/// <returns>A data objext that can be processed by a reader to generate an <see cref="OpenApiDocument"/></returns>
21+
Task<TInput> LoadAsync(Uri uri);
1322
}
1423
}

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ internal async Task<OpenApiDocument> ReadAsync(Stream input, OpenApiDiagnostic d
7878

7979
// Load Document into workspace and load all referenced documents
8080
var workspace = openApiWorkspace ?? new OpenApiWorkspace();
81-
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkspace, new DefaultStreamLoader(), _settings);
81+
var settings = new OpenApiReaderSettings()
82+
{
83+
ExtensionParsers = _settings.ExtensionParsers,
84+
RuleSet = _settings.RuleSet,
85+
ReferenceResolution = ReferenceResolutionSetting.DoNotResolveReferences
86+
};
87+
88+
var reader = new OpenApiStreamReader(settings);
89+
var workspaceLoader = new OpenApiWorkspaceLoader<Stream, OpenApiDiagnostic>(openApiWorkspace, new DefaultStreamLoader(), reader);
8290
await workspaceLoader.LoadAsync(null,document);
8391

8492
// Resolve References if requested

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
namespace Microsoft.OpenApi.Readers.Services
1414
{
1515
/// <summary>
16-
/// Implementation of <see cref="IStreamLoader"/> that loads streams from URIs
16+
/// Implementation of IInputLoader that loads streams from URIs
1717
/// </summary>
18-
internal class DefaultStreamLoader : IStreamLoader
18+
internal class DefaultStreamLoader : IInputLoader<Stream>
1919
{
2020
private HttpClient _httpClient = new HttpClient();
2121

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,7 @@ private void ResolveTags(IList<OpenApiTag> tags)
180180
}
181181
else if (_resolveRemoteReferences == true)
182182
{
183-
// TODO: Resolve Remote reference
184-
return new T()
185-
{
186-
UnresolvedReference = true,
187-
Reference = reference
188-
};
183+
return _currentDocument.Workspace.ResolveReference(reference) as T;
189184
}
190185
else
191186
{

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@
1010

1111
namespace Microsoft.OpenApi.Readers.Services
1212
{
13-
internal class OpenApiWorkspaceLoader
13+
internal class OpenApiWorkspaceLoader<TInput,TDiagnostic> where TDiagnostic: IDiagnostic
1414
{
1515
private OpenApiWorkspace _workspace;
16-
private IStreamLoader _streamLoader;
17-
private OpenApiDiagnostic _diagnostics;
18-
private OpenApiReaderSettings _readerSettings;
16+
private IInputLoader<TInput> _loader;
17+
private TDiagnostic _diagnostics;
18+
private IOpenApiReader<TInput, TDiagnostic> _reader;
1919

20-
public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, IStreamLoader streamloader, OpenApiReaderSettings readerSettings)
20+
public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, IInputLoader<TInput> loader, IOpenApiReader<TInput, TDiagnostic> reader)
2121
{
2222
_workspace = workspace;
23-
_streamLoader = streamloader;
24-
_readerSettings = readerSettings;
25-
_readerSettings.ReferenceResolution = ReferenceResolutionSetting.DoNotResolveReferences;
23+
_loader = loader;
24+
_reader = reader;
2625
}
2726

2827
internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument document)
@@ -41,9 +40,8 @@ internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument docume
4140
// If not already in workspace, load it and process references
4241
if (!_workspace.Contains(item.ExternalResource))
4342
{
44-
var stream = await _streamLoader.LoadAsync(new Uri(item.ExternalResource));
45-
var reader = new OpenApiStreamReader(_readerSettings);
46-
var newDocument = reader.Read(stream, out _diagnostics);
43+
var input = await _loader.LoadAsync(new Uri(item.ExternalResource));
44+
var newDocument = _reader.Read(input, out _diagnostics);
4745
await LoadAsync(item, newDocument);
4846
}
4947
}

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,14 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>
274274
/// <summary>
275275
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
276276
/// </summary>
277-
public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
277+
public IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal = false)
278278
{
279279
if (reference == null)
280280
{
281281
return null;
282282
}
283283

284-
if (reference.IsExternal)
284+
if (reference.IsExternal && !useExternal)
285285
{
286286
// Should not attempt to resolve external references against a single document.
287287
throw new ArgumentException(Properties.SRResource.RemoteReferenceNotSupported);

src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ namespace Microsoft.OpenApi.Services
1616
/// </summary>
1717
public class OpenApiWorkspace
1818
{
19+
private Dictionary<string, OpenApiDocument> _documents = new Dictionary<string, OpenApiDocument>();
1920

20-
public IEnumerable<OpenApiDocument> Documents { get; }
21+
public IEnumerable<OpenApiDocument> Documents {
22+
get {
23+
return _documents.Values;
24+
}
25+
}
2126

2227
public IEnumerable<IOpenApiFragment> Fragments { get; }
2328

@@ -26,9 +31,10 @@ public bool Contains(string location)
2631
{
2732
return true;
2833
}
29-
public void AddDocument(string location, OpenApiDocument document)
30-
{
3134

35+
public void AddDocument(string location, OpenApiDocument document)
36+
{
37+
_documents.Add(location, document);
3238
}
3339

3440
public void AddFragment(string location, IOpenApiFragment fragment)
@@ -38,9 +44,12 @@ public void AddFragment(string location, IOpenApiFragment fragment)
3844

3945
public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
4046
{
41-
// Find the doc/fragment
42-
// Call ResolveReference on it
43-
return null;
47+
if (!_documents.TryGetValue(reference.ExternalResource,out var doc))
48+
{
49+
return null;
50+
}
51+
52+
return doc.ResolveReference(reference,true);
4453
}
4554

4655
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.OpenApi.Readers.Tests.OpenApiWorkspaceTests
8+
{
9+
class OpenApiWorkspaceStreamTests
10+
{
11+
}
12+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.OpenApi.Models;
10+
using Microsoft.OpenApi.Services;
11+
using Xunit;
12+
13+
namespace Microsoft.OpenApi.Tests
14+
{
15+
16+
public class OpenApiWorkspaceTests
17+
{
18+
[Fact]
19+
public void OpenApiWorkspaceCanHoldMultipleDocuments()
20+
{
21+
var workspace = new OpenApiWorkspace();
22+
23+
workspace.AddDocument("root", new OpenApiDocument());
24+
workspace.AddDocument("common", new OpenApiDocument());
25+
26+
Assert.Equal(2, workspace.Documents.Count());
27+
}
28+
29+
[Fact]
30+
public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther()
31+
{
32+
var workspace = new OpenApiWorkspace();
33+
34+
workspace.AddDocument("root", new OpenApiDocument() {
35+
Paths = new OpenApiPaths()
36+
{
37+
["/"] = new OpenApiPathItem()
38+
{
39+
Operations = new Dictionary<OperationType, OpenApiOperation>()
40+
{
41+
[OperationType.Get] = new OpenApiOperation() {
42+
Responses = new OpenApiResponses()
43+
{
44+
["200"] = new OpenApiResponse()
45+
{
46+
Content = new Dictionary<string,OpenApiMediaType>()
47+
{
48+
["application/json"] = new OpenApiMediaType()
49+
{
50+
Schema = new OpenApiSchema()
51+
{
52+
Reference = new OpenApiReference()
53+
{
54+
Id = "test",
55+
Type = ReferenceType.Schema
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
});
67+
workspace.AddDocument("common", new OpenApiDocument() {
68+
Components = new OpenApiComponents()
69+
{
70+
Schemas = {
71+
["test"] = new OpenApiSchema() {
72+
Type = "string",
73+
Description = "The referenced one"
74+
}
75+
}
76+
}
77+
});
78+
79+
Assert.Equal(2, workspace.Documents.Count());
80+
}
81+
82+
[Fact]
83+
public void OpenApiWorkspacesCanResolveExternalReferences()
84+
{
85+
var workspace = new OpenApiWorkspace();
86+
workspace.AddDocument("common", CreateCommonDocument());
87+
var schema = workspace.ResolveReference(new OpenApiReference()
88+
{
89+
Id = "test",
90+
Type = ReferenceType.Schema,
91+
ExternalResource ="common"
92+
}) as OpenApiSchema;
93+
94+
Assert.NotNull(schema);
95+
Assert.Equal("The referenced one", schema.Description);
96+
}
97+
98+
[Fact]
99+
public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short()
100+
{
101+
var workspace = new OpenApiWorkspace();
102+
103+
var doc = new OpenApiDocument();
104+
doc.CreatePathItem("/", p =>
105+
{
106+
p.Description = "Consumer";
107+
p.CreateOperation(OperationType.Get, op =>
108+
op.CreateResponse("200", re =>
109+
{
110+
re.Description = "Success";
111+
re.CreateContent("application/json", co =>
112+
co.Schema = new OpenApiSchema()
113+
{
114+
Reference = new OpenApiReference()
115+
{
116+
Id = "test",
117+
Type = ReferenceType.Schema
118+
},
119+
UnresolvedReference = true
120+
}
121+
);
122+
})
123+
);
124+
});
125+
126+
workspace.AddDocument("root", doc);
127+
128+
workspace.AddDocument("common", CreateCommonDocument());
129+
130+
var schema = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema;
131+
Assert.True(schema.UnresolvedReference);
132+
}
133+
134+
private static OpenApiDocument CreateCommonDocument()
135+
{
136+
return new OpenApiDocument()
137+
{
138+
Components = new OpenApiComponents()
139+
{
140+
Schemas = {
141+
["test"] = new OpenApiSchema() {
142+
Type = "string",
143+
Description = "The referenced one"
144+
}
145+
}
146+
}
147+
};
148+
}
149+
}
150+
151+
152+
153+
154+
public static class OpenApiFactoryExtensions {
155+
156+
public static OpenApiDocument CreatePathItem(this OpenApiDocument document, string path, Action<OpenApiPathItem> config)
157+
{
158+
var pathItem = new OpenApiPathItem();
159+
config(pathItem);
160+
document.Paths.Add(path, pathItem);
161+
return document;
162+
}
163+
164+
public static OpenApiPathItem CreateOperation(this OpenApiPathItem parent, OperationType opType, Action<OpenApiOperation> config)
165+
{
166+
var child = new OpenApiOperation();
167+
config(child);
168+
parent.Operations.Add(opType, child);
169+
return parent;
170+
}
171+
172+
public static OpenApiOperation CreateResponse(this OpenApiOperation parent, string status, Action<OpenApiResponse> config)
173+
{
174+
var child = new OpenApiResponse();
175+
config(child);
176+
parent.Responses.Add(status, child);
177+
return parent;
178+
}
179+
180+
public static OpenApiResponse CreateContent(this OpenApiResponse parent, string mediaType, Action<OpenApiMediaType> config)
181+
{
182+
var child = new OpenApiMediaType();
183+
config(child);
184+
parent.Content.Add(mediaType, child);
185+
return parent;
186+
}
187+
188+
}
189+
}

0 commit comments

Comments
 (0)