-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(references)!: add basic scaffolding for external ref resolution (#…
…168) Co-authored-by: lego-10-01-06[bot] <119427331+lego-10-01-06[bot]@users.noreply.github.com> Co-authored-by: Alex Wichmann <[email protected]> Co-authored-by: gokerakc <[email protected]> Co-authored-by: VisualBean <[email protected]> Co-authored-by: Byron Mayne <[email protected]> Co-authored-by: James Thompson <[email protected]> Co-authored-by: dec.kolakowski <[email protected]> BREAKING CHANGE: Change the `ReferenceResolution` enum.
- Loading branch information
1 parent
cf6cf6d
commit 559d002
Showing
7 changed files
with
461 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
245 changes: 245 additions & 0 deletions
245
src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
using LEGO.AsyncAPI.Readers.Exceptions; | ||
using LEGO.AsyncAPI.Services; | ||
|
||
namespace LEGO.AsyncAPI.Readers | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using LEGO.AsyncAPI.Exceptions; | ||
using LEGO.AsyncAPI.Models; | ||
using LEGO.AsyncAPI.Models.Interfaces; | ||
|
||
/// <summary> | ||
/// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects. | ||
/// </summary> | ||
internal class AsyncApiExternalReferenceResolver : AsyncApiVisitorBase | ||
{ | ||
private AsyncApiDocument currentDocument; | ||
private List<AsyncApiError> errors = new List<AsyncApiError>(); | ||
private AsyncApiReaderSettings readerSettings; | ||
|
||
public AsyncApiExternalReferenceResolver( | ||
AsyncApiDocument currentDocument, | ||
AsyncApiReaderSettings readerSettings) | ||
{ | ||
this.currentDocument = currentDocument; | ||
this.readerSettings = readerSettings; | ||
} | ||
|
||
public IEnumerable<AsyncApiError> Errors | ||
{ | ||
get | ||
{ | ||
return this.errors; | ||
} | ||
} | ||
|
||
public override void Visit(IAsyncApiReferenceable referenceable) | ||
{ | ||
if (referenceable.Reference != null) | ||
{ | ||
referenceable.Reference.HostDocument = this.currentDocument; | ||
} | ||
} | ||
|
||
public override void Visit(AsyncApiComponents components) | ||
{ | ||
this.ResolveMap(components.Parameters); | ||
this.ResolveMap(components.Channels); | ||
this.ResolveMap(components.Schemas); | ||
this.ResolveMap(components.Servers); | ||
this.ResolveMap(components.CorrelationIds); | ||
this.ResolveMap(components.MessageTraits); | ||
this.ResolveMap(components.OperationTraits); | ||
this.ResolveMap(components.SecuritySchemes); | ||
this.ResolveMap(components.ChannelBindings); | ||
this.ResolveMap(components.MessageBindings); | ||
this.ResolveMap(components.OperationBindings); | ||
this.ResolveMap(components.ServerBindings); | ||
this.ResolveMap(components.Messages); | ||
} | ||
|
||
public override void Visit(AsyncApiDocument doc) | ||
{ | ||
this.ResolveMap(doc.Servers); | ||
this.ResolveMap(doc.Channels); | ||
} | ||
|
||
public override void Visit(AsyncApiChannel channel) | ||
{ | ||
this.ResolveMap(channel.Parameters); | ||
this.ResolveObject(channel.Bindings, r => channel.Bindings = r); | ||
} | ||
|
||
public override void Visit(AsyncApiMessageTrait trait) | ||
{ | ||
this.ResolveObject(trait.CorrelationId, r => trait.CorrelationId = r); | ||
this.ResolveObject(trait.Headers, r => trait.Headers = r); | ||
} | ||
|
||
/// <summary> | ||
/// Resolve all references used in an operation. | ||
/// </summary> | ||
public override void Visit(AsyncApiOperation operation) | ||
{ | ||
this.ResolveList(operation.Message); | ||
this.ResolveList(operation.Traits); | ||
this.ResolveObject(operation.Bindings, r => operation.Bindings = r); | ||
} | ||
|
||
public override void Visit(AsyncApiMessage message) | ||
{ | ||
this.ResolveObject(message.Headers, r => message.Headers = r); | ||
this.ResolveObject(message.Payload, r => message.Payload = r); | ||
this.ResolveList(message.Traits); | ||
this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); | ||
this.ResolveObject(message.Bindings, r => message.Bindings = r); | ||
} | ||
|
||
public override void Visit(AsyncApiServer server) | ||
{ | ||
this.ResolveObject(server.Bindings, r => server.Bindings = r); | ||
} | ||
|
||
/// <summary> | ||
/// Resolve all references to SecuritySchemes. | ||
/// </summary> | ||
public override void Visit(AsyncApiSecurityRequirement securityRequirement) | ||
{ | ||
foreach (var scheme in securityRequirement.Keys.ToList()) | ||
{ | ||
this.ResolveObject(scheme, (resolvedScheme) => | ||
{ | ||
if (resolvedScheme != null) | ||
{ | ||
// If scheme was unresolved | ||
// copy Scopes and remove old unresolved scheme | ||
var scopes = securityRequirement[scheme]; | ||
securityRequirement.Remove(scheme); | ||
securityRequirement.Add(resolvedScheme, scopes); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Resolve all references to parameters. | ||
/// </summary> | ||
public override void Visit(IList<AsyncApiParameter> parameters) | ||
{ | ||
this.ResolveList(parameters); | ||
} | ||
|
||
/// <summary> | ||
/// Resolve all references used in a parameter. | ||
/// </summary> | ||
public override void Visit(AsyncApiParameter parameter) | ||
{ | ||
this.ResolveObject(parameter.Schema, r => parameter.Schema = r); | ||
} | ||
|
||
/// <summary> | ||
/// Resolve all references used in a schema. | ||
/// </summary> | ||
public override void Visit(AsyncApiSchema schema) | ||
{ | ||
this.ResolveObject(schema.Items, r => schema.Items = r); | ||
this.ResolveList(schema.OneOf); | ||
this.ResolveList(schema.AllOf); | ||
this.ResolveList(schema.AnyOf); | ||
this.ResolveObject(schema.Contains, r => schema.Contains = r); | ||
this.ResolveObject(schema.Else, r => schema.Else = r); | ||
this.ResolveObject(schema.If, r => schema.If = r); | ||
this.ResolveObject(schema.Items, r => schema.Items = r); | ||
this.ResolveObject(schema.Not, r => schema.Not = r); | ||
this.ResolveObject(schema.Then, r => schema.Then = r); | ||
this.ResolveObject(schema.PropertyNames, r => schema.PropertyNames = r); | ||
this.ResolveObject(schema.AdditionalProperties, r => schema.AdditionalProperties = r); | ||
this.ResolveMap(schema.Properties); | ||
} | ||
|
||
private void ResolveObject<T>(T entity, Action<T> assign) | ||
where T : class, IAsyncApiReferenceable, new() | ||
{ | ||
if (entity == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (this.IsUnresolvedReference(entity)) | ||
{ | ||
assign(this.ResolveReference<T>(entity.Reference)); | ||
} | ||
} | ||
|
||
private void ResolveList<T>(IList<T> list) | ||
where T : class, IAsyncApiReferenceable, new() | ||
{ | ||
if (list == null) | ||
{ | ||
return; | ||
} | ||
|
||
for (int i = 0; i < list.Count; i++) | ||
{ | ||
var entity = list[i]; | ||
if (this.IsUnresolvedReference(entity)) | ||
{ | ||
list[i] = this.ResolveReference<T>(entity.Reference); | ||
} | ||
} | ||
} | ||
|
||
private void ResolveMap<T>(IDictionary<string, T> map) | ||
where T : class, IAsyncApiReferenceable, new() | ||
{ | ||
if (map == null) | ||
{ | ||
return; | ||
} | ||
|
||
foreach (var key in map.Keys.ToList()) | ||
{ | ||
var entity = map[key]; | ||
if (this.IsUnresolvedReference(entity)) | ||
{ | ||
map[key] = this.ResolveReference<T>(entity.Reference); | ||
} | ||
} | ||
} | ||
|
||
private T ResolveReference<T>(AsyncApiReference reference) | ||
where T : class, IAsyncApiReferenceable, new() | ||
{ | ||
if (reference.IsExternal) | ||
{ | ||
if (this.readerSettings.ExternalReferenceReader is null) | ||
{ | ||
throw new AsyncApiReaderException( | ||
"External reference configured in AsyncApi document but no implementation provided for ExternalReferenceReader."); | ||
} | ||
|
||
// read external content | ||
var externalContent = this.readerSettings.ExternalReferenceReader.Load(reference.Reference); | ||
|
||
// read external object content | ||
var reader = new AsyncApiStringReader(this.readerSettings); | ||
var externalAsyncApiContent = reader.ReadFragment<T>(externalContent, AsyncApiVersion.AsyncApi2_0, out var diagnostic); | ||
foreach (var error in diagnostic.Errors) | ||
{ | ||
this.errors.Add(error); | ||
} | ||
|
||
return externalAsyncApiContent; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) | ||
{ | ||
return (possibleReference != null && possibleReference.UnresolvedReference); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.