Skip to content

Commit 661bfdb

Browse files
committed
register root document for document level fragment resolution
1 parent 6816cd1 commit 661bfdb

File tree

7 files changed

+111
-83
lines changed

7 files changed

+111
-83
lines changed

src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -172,45 +172,14 @@ public T ReadFragment<T>(JsonNode input, AsyncApiVersion version, out AsyncApiDi
172172
return (T)element;
173173
}
174174

175-
private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
175+
private void ResolveReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable)
176176
{
177-
switch (this.settings.ReferenceResolution)
177+
if (this.settings.ReferenceResolution == ReferenceResolutionSetting.DoNotResolveReferences)
178178
{
179-
case ReferenceResolutionSetting.ResolveAllReferences:
180-
this.ResolveAllReferences(diagnostic, document);
181-
break;
182-
case ReferenceResolutionSetting.ResolveInternalReferences:
183-
this.ResolveInternalReferences(diagnostic, document);
184-
break;
185-
case ReferenceResolutionSetting.DoNotResolveReferences:
186-
break;
187-
}
188-
}
189-
190-
private void ResolveAllReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
191-
{
192-
this.ResolveInternalReferences(diagnostic, document);
193-
this.ResolveExternalReferences(diagnostic, document, document);
194-
}
195-
196-
private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
197-
{
198-
var reader = new AsyncApiStringReader(this.settings);
199-
200-
var resolver = new AsyncApiReferenceHostDocumentResolver(this.context.Workspace);
201-
var walker = new AsyncApiWalker(resolver);
202-
walker.Walk(document);
203-
204-
foreach (var item in resolver.Errors)
205-
{
206-
diagnostic.Errors.Add(item);
179+
return;
207180
}
208-
}
209181

210-
private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable, AsyncApiDocument hostDocument)
211-
{
212-
var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings);
213-
var collector = new AsyncApiRemoteReferenceCollector(this.context.Workspace);
182+
var collector = new AsyncApiReferenceCollector(this.context.Workspace);
214183
var walker = new AsyncApiWalker(collector);
215184
walker.Walk(serializable);
216185

@@ -221,33 +190,60 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS
221190
continue;
222191
}
223192

224-
try
193+
IAsyncApiSerializable component = null;
194+
if (reference.Reference.IsExternal)
225195
{
226-
Stream stream;
227-
if (this.context.Workspace.Contains(reference.Reference.ExternalResource))
196+
if (this.settings.ReferenceResolution != ReferenceResolutionSetting.ResolveAllReferences)
228197
{
229-
stream = this.context.Workspace.ResolveReference<Stream>(reference.Reference.ExternalResource);
230-
}
231-
else
232-
{
233-
stream = loader.Load(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute));
234-
this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream);
235-
}
236-
237-
var component = this.ResolveStreamReferences(stream, reference, diagnostic);
238-
if (component == null)
239-
{
240-
diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'"));
241198
continue;
242199
}
243200

244-
this.context.Workspace.RegisterComponent(reference.Reference.Reference, component);
245-
this.ResolveExternalReferences(diagnostic, component, hostDocument);
201+
component = this.ResolveExternalReference(diagnostic, reference);
202+
}
203+
else
204+
{
205+
var stream = this.context.Workspace.ResolveReference<Stream>(string.Empty); // get whole document.
206+
component = this.ResolveStreamReference(stream, reference, diagnostic);
207+
}
208+
209+
if (component == null)
210+
{
211+
diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'"));
212+
continue;
213+
}
214+
215+
this.context.Workspace.RegisterComponent(reference.Reference.Reference, component);
216+
this.ResolveReferences(diagnostic, component);
217+
}
218+
}
219+
220+
private IAsyncApiSerializable ResolveExternalReference(AsyncApiDiagnostic diagnostic, IAsyncApiReferenceable reference)
221+
{
222+
if (reference is null)
223+
{
224+
throw new ArgumentNullException(nameof(reference));
225+
}
226+
227+
var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings);
228+
try
229+
{
230+
Stream stream;
231+
if (this.context.Workspace.Contains(reference.Reference.ExternalResource))
232+
{
233+
stream = this.context.Workspace.ResolveReference<Stream>(reference.Reference.ExternalResource);
246234
}
247-
catch (AsyncApiException ex)
235+
else
248236
{
249-
diagnostic.Errors.Add(new AsyncApiError(ex));
237+
stream = loader.Load(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute));
238+
this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream);
250239
}
240+
241+
return this.ResolveStreamReference(stream, reference, diagnostic);
242+
}
243+
catch (AsyncApiException ex)
244+
{
245+
diagnostic.Errors.Add(new AsyncApiError(ex));
246+
return null;
251247
}
252248
}
253249

@@ -264,7 +260,7 @@ private JsonNode ReadToJson(Stream stream)
264260
return default;
265261
}
266262

267-
private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic)
263+
private IAsyncApiSerializable ResolveStreamReference(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic)
268264
{
269265
JsonNode json = null;
270266
try
@@ -274,6 +270,7 @@ private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiRe
274270
catch
275271
{
276272
diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference: '{reference.Reference.Reference}'"));
273+
return null;
277274
}
278275

279276
if (reference.Reference.IsFragment)
@@ -329,19 +326,19 @@ private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiRe
329326
result = this.ReadFragment<AsyncApiOperationTrait>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
330327
break;
331328
case ReferenceType.MessageTrait:
332-
result = this.ReadFragment<AsyncApiMessage>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
329+
result = this.ReadFragment<AsyncApiMessageTrait>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
333330
break;
334331
case ReferenceType.ServerBindings:
335-
result = this.ReadFragment<AsyncApiMessage>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
332+
result = this.ReadFragment<AsyncApiBindings<IServerBinding>>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
336333
break;
337334
case ReferenceType.ChannelBindings:
338-
result = this.ReadFragment<AsyncApiMessage>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
335+
result = this.ReadFragment<AsyncApiBindings<IChannelBinding>>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
339336
break;
340337
case ReferenceType.OperationBindings:
341-
result = this.ReadFragment<AsyncApiMessage>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
338+
result = this.ReadFragment<AsyncApiBindings<IOperationBinding>>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
342339
break;
343340
case ReferenceType.MessageBindings:
344-
result = this.ReadFragment<AsyncApiMessage>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
341+
result = this.ReadFragment<AsyncApiBindings<IMessageBinding>>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic);
345342
break;
346343
default:
347344
diagnostic.Errors.Add(new AsyncApiError(reference.Reference.Reference, "Could not resolve reference."));

src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,16 @@ namespace LEGO.AsyncAPI.Readers
77
using LEGO.AsyncAPI.Models.Interfaces;
88
using LEGO.AsyncAPI.Services;
99

10-
internal class AsyncApiReferenceHostDocumentResolver : AsyncApiVisitorBase
10+
internal class AsyncApiReferenceWorkspaceResolver : AsyncApiVisitorBase
1111
{
1212
private AsyncApiWorkspace workspace;
13-
private List<AsyncApiError> errors = new List<AsyncApiError>();
1413

15-
public AsyncApiReferenceHostDocumentResolver(
14+
public AsyncApiReferenceWorkspaceResolver(
1615
AsyncApiWorkspace workspace)
1716
{
1817
this.workspace = workspace;
1918
}
2019

21-
public IEnumerable<AsyncApiError> Errors
22-
{
23-
get
24-
{
25-
return this.errors;
26-
}
27-
}
28-
2920
public override void Visit(IAsyncApiReferenceable referenceable)
3021
{
3122
if (referenceable.Reference != null)

src/LEGO.AsyncAPI.Readers/ParsingContext.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ namespace LEGO.AsyncAPI.Readers
44
{
55
using System;
66
using System.Collections.Generic;
7+
using System.IO;
78
using System.Linq;
9+
using System.Text.Json;
810
using System.Text.Json.Nodes;
911
using LEGO.AsyncAPI.Models;
1012
using LEGO.AsyncAPI.Models.Interfaces;
@@ -81,7 +83,10 @@ internal AsyncApiDocument Parse(JsonNode jsonNode)
8183
case string version when version.StartsWith("2"):
8284
this.VersionService = new AsyncApiV2VersionService(this.Diagnostic);
8385
doc = this.VersionService.LoadDocument(this.RootNode);
84-
this.Workspace.RegisterComponents(doc);
86+
87+
// Register components
88+
this.Workspace.RegisterComponents(doc); // pre-register components.
89+
this.Workspace.RegisterComponent(string.Empty, this.ParseToStream(jsonNode)); // register root document.
8590
this.Diagnostic.SpecificationVersion = AsyncApiVersion.AsyncApi2_0;
8691
break;
8792

@@ -92,6 +97,18 @@ internal AsyncApiDocument Parse(JsonNode jsonNode)
9297
return doc;
9398
}
9499

100+
private Stream ParseToStream(JsonNode node)
101+
{
102+
var stream = new MemoryStream();
103+
using (var writer = new Utf8JsonWriter(stream))
104+
{
105+
node.WriteTo(writer);
106+
}
107+
108+
stream.Position = 0;
109+
return stream;
110+
}
111+
95112
internal T ParseFragment<T>(JsonNode jsonNode, AsyncApiVersion version)
96113
where T : IAsyncApiElement
97114
{

src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
namespace LEGO.AsyncAPI.Readers.Services
44
{
55
using System.Collections.Generic;
6-
using LEGO.AsyncAPI.Models;
76
using LEGO.AsyncAPI.Models.Interfaces;
87
using LEGO.AsyncAPI.Services;
98

10-
internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase
9+
internal class AsyncApiReferenceCollector : AsyncApiVisitorBase
1110
{
1211
private readonly List<IAsyncApiReferenceable> references = new();
1312
private AsyncApiWorkspace workspace;
1413

15-
public AsyncApiRemoteReferenceCollector(
14+
public AsyncApiReferenceCollector(
1615
AsyncApiWorkspace workspace)
1716
{
1817
this.workspace = workspace;
@@ -34,7 +33,7 @@ public IEnumerable<IAsyncApiReferenceable> References
3433
/// <param name="referenceable"></param>
3534
public override void Visit(IAsyncApiReferenceable referenceable)
3635
{
37-
if (referenceable.Reference != null && referenceable.Reference.IsExternal)
36+
if (referenceable.Reference != null)
3837
{
3938
if (referenceable.Reference.Workspace == null)
4039
{

src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic)
4444
[typeof(AsyncApiServerVariable)] = AsyncApiV2Deserializer.LoadServerVariable,
4545
[typeof(AsyncApiTag)] = AsyncApiV2Deserializer.LoadTag,
4646
[typeof(AsyncApiMessage)] = AsyncApiV2Deserializer.LoadMessage,
47+
[typeof(AsyncApiMessageTrait)] = AsyncApiV2Deserializer.LoadMessageTrait,
4748
[typeof(AsyncApiChannel)] = AsyncApiV2Deserializer.LoadChannel,
49+
[typeof(AsyncApiBindings<IServerBinding>)] = AsyncApiV2Deserializer.LoadServerBindings,
50+
[typeof(AsyncApiBindings<IChannelBinding>)] = AsyncApiV2Deserializer.LoadChannelBindings,
51+
[typeof(AsyncApiBindings<IMessageBinding>)] = AsyncApiV2Deserializer.LoadMessageBindings,
52+
[typeof(AsyncApiBindings<IOperationBinding>)] = AsyncApiV2Deserializer.LoadOperationBindings,
4853
};
4954

5055
/// <summary>

src/LEGO.AsyncAPI/AsyncApiWorkspace.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,20 +169,16 @@ public T ResolveReference<T>(AsyncApiReference reference)
169169
public T ResolveReference<T>(string location)
170170
where T : class
171171
{
172-
if (string.IsNullOrEmpty(location))
173-
{
174-
return default;
175-
}
176-
177172
var uri = this.ToLocationUrl(location);
178173
if (this.resolvedReferenceRegistry.TryGetValue(uri, out var referenceableValue))
179174
{
180175
return referenceableValue as T;
181176
}
182177

183-
if (this.artifactsRegistry.TryGetValue(uri, out var json))
178+
if (this.artifactsRegistry.TryGetValue(uri, out var stream))
184179
{
185-
return (T)(object)json;
180+
stream.Position = 0;
181+
return (T)(object)stream;
186182
}
187183

188184
return default;

test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,29 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect
436436
payload.Properties.Count.Should().Be(1);
437437
}
438438

439+
[Test]
440+
public void AsyncApiReference_DocumentLevelReferencePointer_DeserializesCorrectly()
441+
{
442+
var yaml = """
443+
asyncapi: 2.3.0
444+
info:
445+
title: test
446+
version: 1.0.0
447+
channels:
448+
workspace:
449+
publish:
450+
message:
451+
title: test message
452+
other:
453+
$ref: "#/channels/workspace"
454+
""";
455+
456+
var reader = new AsyncApiStringReader();
457+
var doc = reader.Read(yaml, out var diagnostic);
458+
doc.Channels.Should().HaveCount(2);
459+
doc.Channels["other"].Publish.Message.First().Title.Should().Be("test message");
460+
}
461+
439462
[Test]
440463
public void AsyncApiReference_WithExternalAvroResource_DeserializesCorrectly()
441464
{

0 commit comments

Comments
 (0)