From c1e04fca3b8210a537698b817f561391f8131f24 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 2 Jul 2024 13:04:33 +0200 Subject: [PATCH 01/20] refs --- .../AsyncApiDiagnostics.cs | 16 ++ .../AsyncApiExternalReferenceResolver.cs | 247 ------------------ .../AsyncApiJsonDocumentReader.cs | 81 +++++- .../AsyncApiReaderSettings.cs | 8 +- .../IAsyncApiExternalReferenceReader.cs | 14 - .../AsyncApiRemoteReferenceCollector.cs | 46 ++++ .../Services/DefaultStreamLoader.cs | 16 +- .../V2/AsyncApiV2VersionService.cs | 9 +- .../Services/AsyncApiReferenceResolver.cs | 6 +- .../Models/AsyncApiMessage_Should.cs | 1 + .../Models/AsyncApiReference_Should.cs | 77 +++--- 11 files changed, 192 insertions(+), 329 deletions(-) delete mode 100644 src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs delete mode 100644 src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs create mode 100644 src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs index faceb39e..b80488b8 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs @@ -13,5 +13,21 @@ public class AsyncApiDiagnostic : IDiagnostic public IList Warnings { get; set; } = new List(); public AsyncApiVersion SpecificationVersion { get; set; } + + public void Append(AsyncApiDiagnostic diagnosticToAdd, string fileNameToAdd = null) + { + var fileNameIsSupplied = !string.IsNullOrEmpty(fileNameToAdd); + foreach (var error in diagnosticToAdd.Errors) + { + var errMsgWithFileName = fileNameIsSupplied ? $"[File: {fileNameToAdd}] {error.Message}" : error.Message; + this.Errors.Add(new(error.Pointer, errMsgWithFileName)); + } + + foreach (var warning in diagnosticToAdd.Warnings) + { + var warnMsgWithFileName = fileNameIsSupplied ? $"[File: {fileNameToAdd}] {warning.Message}" : warning.Message; + this.Warnings.Add(new(warning.Pointer, warnMsgWithFileName)); + } + } } } diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs deleted file mode 100644 index 2c111773..00000000 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ /dev/null @@ -1,247 +0,0 @@ -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.Models; - using LEGO.AsyncAPI.Models.Interfaces; - - /// - /// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects. - /// - internal class AsyncApiExternalReferenceResolver : AsyncApiVisitorBase - { - private AsyncApiDocument currentDocument; - private List errors = new List(); - private AsyncApiReaderSettings readerSettings; - - public AsyncApiExternalReferenceResolver( - AsyncApiDocument currentDocument, - AsyncApiReaderSettings readerSettings) - { - this.currentDocument = currentDocument; - this.readerSettings = readerSettings; - } - - public IEnumerable 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); - } - - /// - /// Resolve all references used in an operation. - /// - 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); - if (message.Payload is AsyncApiJsonSchemaPayload) - { - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); - } - - /// - /// Resolve all references to SecuritySchemes. - /// - 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); - } - }); - } - } - - /// - /// Resolve all references to parameters. - /// - public override void Visit(IList parameters) - { - this.ResolveList(parameters); - } - - /// - /// Resolve all references used in a parameter. - /// - public override void Visit(AsyncApiParameter parameter) - { - this.ResolveObject(parameter.Schema, r => parameter.Schema = r); - } - - /// - /// Resolve all references used in a schema. - /// - 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 entity, Action assign) - where T : class, IAsyncApiReferenceable, new() - { - if (entity == null) - { - return; - } - - if (this.IsUnresolvedReference(entity)) - { - assign(this.ResolveReference(entity.Reference)); - } - } - - private void ResolveList(IList 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(entity.Reference); - } - } - } - - private void ResolveMap(IDictionary 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(entity.Reference); - } - } - } - - private T ResolveReference(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(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); - } - } -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 6b340651..c5d5ccdb 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -2,6 +2,7 @@ namespace LEGO.AsyncAPI.Readers { + using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Nodes; @@ -12,6 +13,7 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; + using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Services; using LEGO.AsyncAPI.Validations; @@ -200,13 +202,84 @@ private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) { - var resolver = new AsyncApiExternalReferenceResolver(document, this.settings); - var walker = new AsyncApiWalker(resolver); + var loader = this.settings.ExternalReferenceLoader ?? new DefaultStreamLoader(this.settings.BaseUrl); + var collector = new AsyncApiRemoteReferenceCollector(); + var walker = new AsyncApiWalker(collector); walker.Walk(document); - foreach (var error in resolver.Errors) + var reader = new AsyncApiStreamReader(this.settings); + foreach (var reference in collector.References) { - diagnostic.Errors.Add(error); + var input = loader.Load(new Uri(reference.ExternalResource, UriKind.RelativeOrAbsolute)); + + // If Id is not null, the reference is for a fragment of a full document. + if (reference.Id != null) + { + var result = reader.Read(input, out var streamDiagnostics); // How about avro?! + if (streamDiagnostics.Warnings.Any() || streamDiagnostics.Errors.Any()) + { + diagnostic.Append(streamDiagnostics, reference.ExternalResource); + } + if (result != null) + { + this.ResolveExternalReferences(diagnostic, result); + } + } + else + { + // If id IS null, its a fragment that we can resolve directly. + // #TODO Use proxy references for easier fragment resolution. + IAsyncApiElement result; + switch (reference.Type) + { + case ReferenceType.Schema: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); + break; + case ReferenceType.Server: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Channel: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Message: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.SecurityScheme: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Parameter: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.CorrelationId: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.OperationTrait: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.MessageTrait: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.ServerBindings: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.ChannelBindings: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.OperationBindings: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.MessageBindings: + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + break; + default: + diagnostic.Errors.Add(new AsyncApiError(reference.Reference, "Could not resolve reference.")); + break; + } + } + //if (result != null) + //{ + // this.ResolveExternalReferences(diagnostic, result); + //} } } } diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index 341a743c..523e1484 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -8,6 +8,7 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; + using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Validations; public enum ReferenceResolutionSetting @@ -72,6 +73,11 @@ public ICollection> /// /// External reference reader implementation provided by users for reading external resources. /// - public IAsyncApiExternalReferenceReader ExternalReferenceReader { get; set; } + public IStreamLoader ExternalReferenceLoader { get; set; } = null; + + /// + /// URL where relative references should be resolved from if. + /// + public Uri BaseUrl { get; set; } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs deleted file mode 100644 index 6eef5608..00000000 --- a/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace LEGO.AsyncAPI.Readers; - -/// -/// Interface that provides method for reading external references.å. -/// -public interface IAsyncApiExternalReferenceReader -{ - /// - /// Method that returns the AsyncAPI content that the external reference from the $ref points to. - /// - /// The content address of the $ref. - /// The content of the reference as a string. - public string Load(string reference); -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs new file mode 100644 index 00000000..37fde872 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -0,0 +1,46 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers.Services +{ + using System.Collections.Generic; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Services; + + internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase + { + private readonly Dictionary references = new(); + + /// + /// List of all external references collected from AsyncApiDocument. + /// + public IEnumerable References + { + get + { + return this.references.Values; + } + } + + /// + /// Collect reference for each reference. + /// + /// + public override void Visit(IAsyncApiReferenceable referenceable) + { + this.AddExternalReferences(referenceable.Reference); + } + + /// + /// Collect external references. + /// + private void AddExternalReferences(AsyncApiReference reference) + { + if (reference is { IsExternal: true } && + !this.references.ContainsKey(reference.ExternalResource)) + { + this.references.Add(reference.ExternalResource, reference); + } + } + } +} diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs index 6e506730..18f41873 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs @@ -20,14 +20,15 @@ public DefaultStreamLoader(Uri baseUrl) public Stream Load(Uri uri) { - var absoluteUri = new Uri(this.baseUrl, uri); + + uri = new Uri(this.baseUrl, uri); switch (uri.Scheme) { case "file": - return File.OpenRead(absoluteUri.AbsolutePath); + return File.OpenRead(uri.AbsolutePath); case "http": case "https": - return this.httpClient.GetStreamAsync(absoluteUri).GetAwaiter().GetResult(); + return this.httpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); default: throw new ArgumentException("Unsupported scheme"); } @@ -35,15 +36,14 @@ public Stream Load(Uri uri) public async Task LoadAsync(Uri uri) { - var absoluteUri = new Uri(this.baseUrl, uri); - - switch (absoluteUri.Scheme) + uri = new Uri(this.baseUrl, uri); + switch (uri.Scheme) { case "file": - return File.OpenRead(absoluteUri.AbsolutePath); + return File.OpenRead(uri.AbsolutePath); case "http": case "https": - return await this.httpClient.GetStreamAsync(absoluteUri); + return await this.httpClient.GetStreamAsync(uri); default: throw new ArgumentException("Unsupported scheme"); } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 4acc3395..52ca2494 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -76,18 +76,13 @@ public AsyncApiReference ConvertToAsyncApiReference( var asyncApiReference = new AsyncApiReference(); asyncApiReference.Type = type; - if (reference.StartsWith("/")) - { - asyncApiReference.IsFragment = true; - } - asyncApiReference.ExternalResource = segments[0]; return asyncApiReference; } else if (segments.Length == 2) { - // Local reference + // Local components reference if (reference.StartsWith("#")) { try @@ -101,8 +96,8 @@ public AsyncApiReference ConvertToAsyncApiReference( } } - var id = segments[1]; var asyncApiReference = new AsyncApiReference(); + var id = segments[1]; if (id.StartsWith("/components/")) { var localSegments = segments[1].Split('/'); diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index af1176c3..ebf9b52b 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -87,12 +87,16 @@ public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - // #ToFix Resolve references correctly if (message.Payload is AsyncApiJsonSchemaPayload) { this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); } + if (message.Payload is AsyncApiAvroSchemaPayload) + { + this.ResolveObject(message.Payload as AsyncApiAvroSchemaPayload, r => message.Payload = r); + } + this.ResolveList(message.Traits); this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); this.ResolveObject(message.Bindings, r => message.Bindings = r); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index 538664a8..487b60e5 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -234,6 +234,7 @@ public void AsyncApiMessage_WithAvroAsReference_Deserializes() var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); // Assert + deserializedMessage.Payload.UnresolvedReference.Should().BeTrue(); deserializedMessage.Payload.Reference.Should().NotBeNull(); deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 41972784..eca148a1 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -2,10 +2,14 @@ namespace LEGO.AsyncAPI.Tests { + using System; + using System.IO; using System.Linq; + using System.Threading.Tasks; using FluentAssertions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; + using LEGO.AsyncAPI.Readers.Interface; using NUnit.Framework; public class AsyncApiReference_Should : TestBase @@ -61,7 +65,7 @@ public void AsyncApiReference_WithFragmentReference_AllowReference() reference.Type.Should().Be(ReferenceType.Schema); reference.ExternalResource.Should().Be("/fragments/myFragment"); reference.Id.Should().BeNull(); - reference.IsFragment.Should().BeTrue(); + reference.IsFragment.Should().BeFalse(); reference.IsExternal.Should().BeTrue(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); actual.Should() @@ -304,7 +308,7 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceReader = new MockExternalReferenceReader(), + ExternalReferenceLoader = new MockLoader(), }; var reader = new AsyncApiStringReader(settings); var doc = reader.Read(yaml, out var diagnostic); @@ -315,53 +319,32 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect } } - public class MockExternalReferenceReader : IAsyncApiExternalReferenceReader + public class MockLoader : IStreamLoader { - public string Load(string reference) + const string Message = + """ + name: Test + title: Test message + summary: Test. + schemaFormat: application/schema+yaml;version=draft-07 + contentType: application/cloudevents+json + payload: + $ref: "./some/path/to/schema.yaml" + """; + + public Stream Load(Uri uri) { - if (reference == "./some/path/to/external/message.yaml") - { - return """ - name: Test - title: Test message - summary: Test. - schemaFormat: application/schema+yaml;version=draft-07 - contentType: application/cloudevents+json - payload: - $ref: "./some/path/to/schema.yaml" - """; - } - - return """ - type: object - properties: - orderId: - description: The ID of the order. - type: string - format: uuid - name: - description: Name of order. - type: string - orderDetails: - description: User details. - type: object - properties: - userId: - description: User Id. - type: string - format: uuid - userName: - description: User name. - type: string - required: - - orderId - example: - orderId: 8f9189f8-653b-4849-a1ec-c838c030bd67 - handler: SomeName - orderDetails: - userId: Admin - userName: Admin - """; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(Message); + writer.Flush(); + stream.Position = 0; + return stream; + } + + public Task LoadAsync(Uri uri) + { + return Task.FromResult(this.Load(uri)); } } } \ No newline at end of file From f9b2cf0c1ba89d7f188cf48c315b56969ffffac0 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 29 Jul 2024 11:14:49 +0200 Subject: [PATCH 02/20] so far --- .../Http/HttpMessageBinding.cs | 2 +- .../Http/HttpOperationBinding.cs | 2 +- .../Kafka/KafkaMessageBinding.cs | 2 +- .../Kafka/KafkaOperationBinding.cs | 4 +- .../MQTT/MQTTMessageBinding.cs | 2 +- .../WebSockets/WebSocketsChannelBinding.cs | 4 +- .../AsyncApiJsonDocumentReader.cs | 19 ++- .../ParseNodes/AnyFieldMapParameter.cs | 4 +- .../ParseNodes/AnyListFieldMapParameter{T}.cs | 4 +- .../AnyMapFieldMapParameter{T,U}.cs | 4 +- .../V2/AsyncApiSchemaDeserializer.cs | 10 +- .../V2/AsyncApiV2VersionService.cs | 2 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 85 ++++++++++ .../Models/AsyncApiComponents.cs | 8 +- ...syncApiSchema.cs => AsyncApiJsonSchema.cs} | 36 ++--- src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 2 +- .../Models/AsyncApiMessageTrait.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs | 2 +- .../Models/AsyncApiSchemaPayload.cs | 38 ++--- .../Models/JsonSchema/FalseApiSchema.cs | 4 +- .../Services/AsyncApiReferenceResolver.cs | 8 +- .../Services/AsyncApiVisitorBase.cs | 4 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 8 +- .../Validation/AsyncApiValidator.cs | 4 +- .../AsyncApiDocumentBuilder.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 42 ++--- .../Bindings/Http/HttpBindings_Should.cs | 4 +- .../Bindings/Kafka/KafkaBindings_Should.cs | 6 +- .../WebSockets/WebSocketBindings_Should.cs | 4 +- .../MQTT/MQTTBindings_Should.cs | 2 +- .../Models/AsyncApiChannel_Should.cs | 12 +- .../Models/AsyncApiMessage_Should.cs | 20 +-- .../Models/AsyncApiOperation_Should.cs | 6 +- .../Models/AsyncApiSchema_Should.cs | 150 +++++++++--------- 34 files changed, 296 insertions(+), 212 deletions(-) create mode 100644 src/LEGO.AsyncAPI/AsyncApiWorkspace.cs rename src/LEGO.AsyncAPI/Models/{AsyncApiSchema.cs => AsyncApiJsonSchema.cs} (93%) diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs index cbf65bc4..b6c7bf78 100644 --- a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs @@ -16,7 +16,7 @@ public class HttpMessageBinding : MessageBinding /// /// A Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type object and have a properties key. /// - public AsyncApiSchema Headers { get; set; } + public AsyncApiJsonSchema Headers { get; set; } /// /// Serialize to AsyncAPI V2 document without using reference. diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs index 7b88d31b..b7f1215e 100644 --- a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs @@ -36,7 +36,7 @@ public enum HttpOperationType /// /// A Schema object containing the definitions for each query parameter. This schema MUST be of type object and have a properties key. /// - public AsyncApiSchema Query { get; set; } + public AsyncApiJsonSchema Query { get; set; } /// /// Serialize to AsyncAPI V2 document without using reference. diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs index 2e57b50a..e63c95e1 100644 --- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs @@ -16,7 +16,7 @@ public class KafkaMessageBinding : MessageBinding /// /// The message key. NOTE: You can also use the reference object way. /// - public AsyncApiSchema Key { get; set; } + public AsyncApiJsonSchema Key { get; set; } /// /// If a Schema Registry is used when performing this operation, tells where the id of schema is stored (e.g. header or payload). diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs index b6c6e046..358a929b 100644 --- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs @@ -16,12 +16,12 @@ public class KafkaOperationBinding : OperationBinding /// /// Id of the consumer group. /// - public AsyncApiSchema GroupId { get; set; } + public AsyncApiJsonSchema GroupId { get; set; } /// /// Id of the consumer inside a consumer group. /// - public AsyncApiSchema ClientId { get; set; } + public AsyncApiJsonSchema ClientId { get; set; } public override string BindingKey => "kafka"; diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs index 597cc4c7..338230ac 100644 --- a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs @@ -22,7 +22,7 @@ public class MQTTMessageBinding : MessageBinding /// /// Correlation Data is used to identify the request the response message is for. /// - public AsyncApiSchema CorrelationData { get; set; } + public AsyncApiJsonSchema CorrelationData { get; set; } /// /// String describing the content type of the message payload. diff --git a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs index 8827bea4..4321c879 100644 --- a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs @@ -18,12 +18,12 @@ public class WebSocketsChannelBinding : ChannelBinding /// /// A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a 'properties' key. /// - public AsyncApiSchema Query { get; set; } + public AsyncApiJsonSchema Query { get; set; } /// /// A Schema object containing the definitions of the HTTP headers to use when establishing the connection. This schma MUST be of type 'object' and have a 'properties' key. /// - public AsyncApiSchema Headers { get; set; } + public AsyncApiJsonSchema Headers { get; set; } public override string BindingKey => "websockets"; diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index c5d5ccdb..c1664c49 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -220,20 +220,21 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo { diagnostic.Append(streamDiagnostics, reference.ExternalResource); } + if (result != null) { - this.ResolveExternalReferences(diagnostic, result); + this.ResolveAllReferences(diagnostic, result); } } else { // If id IS null, its a fragment that we can resolve directly. // #TODO Use proxy references for easier fragment resolution. - IAsyncApiElement result; + IAsyncApiElement result = null; switch (reference.Type) { case ReferenceType.Schema: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); + result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); break; case ReferenceType.Server: result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); @@ -275,11 +276,13 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo diagnostic.Errors.Add(new AsyncApiError(reference.Reference, "Could not resolve reference.")); break; } - } - //if (result != null) - //{ - // this.ResolveExternalReferences(diagnostic, result); - //} + + if (result != null) + { + this.ResolveAllReferences(diagnostic, document); + } + } + } } } diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyFieldMapParameter.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyFieldMapParameter.cs index 76e9008a..98ded2f6 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyFieldMapParameter.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyFieldMapParameter.cs @@ -10,7 +10,7 @@ internal class AnyFieldMapParameter public AnyFieldMapParameter( Func propertyGetter, Action propertySetter, - Func schemaGetter) + Func schemaGetter) { this.PropertyGetter = propertyGetter; this.PropertySetter = propertySetter; @@ -21,6 +21,6 @@ public AnyFieldMapParameter( public Action PropertySetter { get; } - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyListFieldMapParameter{T}.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyListFieldMapParameter{T}.cs index ee1af993..15aafb4f 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyListFieldMapParameter{T}.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyListFieldMapParameter{T}.cs @@ -11,7 +11,7 @@ internal class AnyListFieldMapParameter public AnyListFieldMapParameter( Func> propertyGetter, Action> propertySetter, - Func schemaGetter) + Func schemaGetter) { this.PropertyGetter = propertyGetter; this.PropertySetter = propertySetter; @@ -22,6 +22,6 @@ public AnyListFieldMapParameter( public Action> PropertySetter { get; } - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyMapFieldMapParameter{T,U}.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyMapFieldMapParameter{T,U}.cs index 2399fe31..9b36f6fa 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyMapFieldMapParameter{T,U}.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/AnyMapFieldMapParameter{T,U}.cs @@ -12,7 +12,7 @@ public AnyMapFieldMapParameter( Func> propertyMapGetter, Func propertyGetter, Action propertySetter, - Func schemaGetter) + Func schemaGetter) { this.PropertyMapGetter = propertyMapGetter; this.PropertyGetter = propertyGetter; @@ -26,6 +26,6 @@ public AnyMapFieldMapParameter( public Action PropertySetter { get; } - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs index b0c10de7..ed774597 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs @@ -11,7 +11,7 @@ namespace LEGO.AsyncAPI.Readers public class AsyncApiSchemaDeserializer { - private static readonly FixedFieldMap schemaFixedFields = new() + private static readonly FixedFieldMap schemaFixedFields = new() { { "title", (a, n) => { a.Title = n.GetScalarValue(); } @@ -209,13 +209,13 @@ public class AsyncApiSchemaDeserializer }, }; - private static readonly PatternFieldMap schemaPatternFields = + private static readonly PatternFieldMap schemaPatternFields = new() { { s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, AsyncApiV2Deserializer.LoadExtension(p, n)) }, }; - public static AsyncApiSchema LoadSchema(ParseNode node) + public static AsyncApiJsonSchema LoadSchema(ParseNode node) { var mapNode = node.CheckMapNode(AsyncApiConstants.Schema); @@ -223,14 +223,14 @@ public static AsyncApiSchema LoadSchema(ParseNode node) if (pointer != null) { - return new AsyncApiSchema + return new AsyncApiJsonSchema { UnresolvedReference = true, Reference = node.Context.VersionService.ConvertToAsyncApiReference(pointer, ReferenceType.Schema), }; } - var schema = new AsyncApiSchema(); + var schema = new AsyncApiJsonSchema(); foreach (var propertyNode in mapNode) { diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 52ca2494..c060455e 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -35,7 +35,7 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiOAuthFlows)] = AsyncApiV2Deserializer.LoadOAuthFlows, [typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation, [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, - [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema, + [typeof(AsyncApiJsonSchema)] = AsyncApiSchemaDeserializer.LoadSchema, [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs new file mode 100644 index 00000000..64ca1c94 --- /dev/null +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -0,0 +1,85 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI +{ + using System; + using System.Collections.Generic; + using System.IO; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + + public class AsyncApiWorkspace + { + private readonly Dictionary documentsIdRegistry = new(); + private readonly Dictionary artifactsRegistry = new(); + private readonly Dictionary asyncApiReferenceableRegistry = new(); + + public Uri BaseUrl { get; } + + public AsyncApiWorkspace() + { + this.BaseUrl = new Uri(AsyncApiConstants.BaseRegistryUri); + } + + public bool RegisterComponent(string location, T component) + { + var uri = this.ToLocationUrl(location); + if (component is IAsyncApiReferenceable referenceable) + { + if (!this.asyncApiReferenceableRegistry.ContainsKey(uri)) + { + this.asyncApiReferenceableRegistry[uri] = referenceable; + return true; + } + } + else if (component is Stream stream) + { + if (!this.artifactsRegistry.ContainsKey(uri)) + { + this.artifactsRegistry[uri] = stream; + return true; + } + } + + return false; + } + + public void AddDocumentId(string key, Uri value) + { + if (!this.documentsIdRegistry.ContainsKey(key)) + { + this.documentsIdRegistry[key] = value; + } + } + + public Uri GetDocumentId(string key) + { + if (this.documentsIdRegistry.TryGetValue(key, out var id)) + { + return id; + } + return null; + } + + public T ResolveReference(string location) + { + if (string.IsNullOrEmpty(location)) + { + return default; + } + + var uri = this.ToLocationUrl(location); + if (this.asyncApiReferenceableRegistry.TryGetValue(uri, out var referenceableValue)) + { + return (T)referenceableValue; + } + + return default; + } + + private Uri ToLocationUrl(string location) + { + return new(this.BaseUrl, location); + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index 6c8a2a4c..b64f5a66 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -19,7 +19,7 @@ public class AsyncApiComponents : IAsyncApiExtensible, IAsyncApiSerializable /// /// An object to hold reusable Schema Objects. /// - public IDictionary Schemas { get; set; } = new Dictionary(); + public IDictionary Schemas { get; set; } = new Dictionary(); /// /// An object to hold reusable Server Objects. @@ -101,10 +101,10 @@ public void SerializeV2(IAsyncApiWriter writer) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); - if (loops.TryGetValue(typeof(AsyncApiSchema), out List schemas)) + if (loops.TryGetValue(typeof(AsyncApiJsonSchema), out List schemas)) { - var asyncApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.Id); + var asyncApiSchemas = schemas.Cast().Distinct().ToList() + .ToDictionary(k => k.Reference.Id); writer.WriteOptionalMap( AsyncApiConstants.Schemas, diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchema.cs similarity index 93% rename from src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs rename to src/LEGO.AsyncAPI/Models/AsyncApiJsonSchema.cs index 3244017b..dab9893d 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchema.cs @@ -11,7 +11,7 @@ namespace LEGO.AsyncAPI.Models /// /// The Schema Object allows the definition of input and output data types. /// - public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyncApiSerializable + public class AsyncApiJsonSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyncApiSerializable { /// /// follow JSON Schema definition. Short text providing information about the data. @@ -107,45 +107,45 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AllOf { get; set; } = new List(); + public IList AllOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList OneOf { get; set; } = new List(); + public IList OneOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AnyOf { get; set; } = new List(); + public IList AnyOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public AsyncApiSchema Not { get; set; } + public AsyncApiJsonSchema Not { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiSchema Contains { get; set; } + public AsyncApiJsonSchema Contains { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiSchema If { get; set; } + public AsyncApiJsonSchema If { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiSchema Then { get; set; } + public AsyncApiJsonSchema Then { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiSchema Else { get; set; } + public AsyncApiJsonSchema Else { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. @@ -157,14 +157,14 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object /// and not a standard JSON Schema. items MUST be present if the type is array. /// - public AsyncApiSchema Items { get; set; } + public AsyncApiJsonSchema Items { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object /// and not a standard JSON Schema. items MUST be present if the type is array. /// - public AsyncApiSchema AdditionalItems { get; set; } + public AsyncApiJsonSchema AdditionalItems { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. @@ -185,7 +185,7 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced). /// - public IDictionary Properties { get; set; } = new Dictionary(); + public IDictionary Properties { get; set; } = new Dictionary(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. @@ -202,14 +202,14 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// Value can be boolean or object. Inline or referenced schema /// MUST be of a Schema Object and not a standard JSON Schema. /// - public AsyncApiSchema AdditionalProperties { get; set; } + public AsyncApiJsonSchema AdditionalProperties { get; set; } - public IDictionary PatternProperties { get; set; } = new Dictionary(); + public IDictionary PatternProperties { get; set; } = new Dictionary(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiSchema PropertyNames { get; set; } + public AsyncApiJsonSchema PropertyNames { get; set; } /// /// adds support for polymorphism. @@ -455,15 +455,15 @@ public void SerializeV2(IAsyncApiWriter writer) if (this.Reference != null) { - settings.LoopDetector.PopLoop(); + settings.LoopDetector.PopLoop(); } } - public AsyncApiSchema GetReferenced(AsyncApiDocument document) + public AsyncApiJsonSchema GetReferenced(AsyncApiDocument document) { if (this.Reference != null && document != null) { - return document.ResolveReference(this.Reference); + return document.ResolveReference(this.Reference); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs index b4848bf6..e0cc6776 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs @@ -20,7 +20,7 @@ public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiReferenceable, IAsy /// /// schema definition of the application headers. Schema MUST be of type "object". /// - public AsyncApiSchema Headers { get; set; } + public AsyncApiJsonSchema Headers { get; set; } /// /// definition of the message payload. It can be of any type but defaults to Schema object. It must match the schema format, including encoding type - e.g Avro should be inlined as either a YAML or JSON object NOT a string to be parsed as YAML or JSON. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs index 856fb7cd..c14a9a24 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs @@ -20,7 +20,7 @@ public class AsyncApiMessageTrait : IAsyncApiExtensible, IAsyncApiReferenceable, /// /// schema definition of the application headers. Schema MUST be of type "object". /// - public AsyncApiSchema Headers { get; set; } + public AsyncApiJsonSchema Headers { get; set; } /// /// definition of the correlation ID used for message tracing or matching. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs index fd925c7b..4c3ef683 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs @@ -20,7 +20,7 @@ public class AsyncApiParameter : IAsyncApiReferenceable, IAsyncApiExtensible, IA /// /// Gets or sets definition of the parameter. /// - public AsyncApiSchema Schema { get; set; } + public AsyncApiJsonSchema Schema { get; set; } /// /// Gets or sets a runtime expression that specifies the location of the parameter value. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs index 19db07b2..ffe0c05b 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -8,14 +8,14 @@ namespace LEGO.AsyncAPI.Models public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload { - private readonly AsyncApiSchema schema; + private readonly AsyncApiJsonSchema schema; public AsyncApiJsonSchemaPayload() { - this.schema = new AsyncApiSchema(); + this.schema = new AsyncApiJsonSchema(); } - public AsyncApiJsonSchemaPayload(AsyncApiSchema schema) + public AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) { this.schema = schema; } @@ -50,27 +50,27 @@ public AsyncApiJsonSchemaPayload(AsyncApiSchema schema) public bool WriteOnly { get => this.schema.WriteOnly; set => this.schema.WriteOnly = value; } - public IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; } + public IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; } - public IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; } + public IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; } - public IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; } + public IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; } - public AsyncApiSchema Not { get => this.schema.Not; set => this.schema.Not = value; } + public AsyncApiJsonSchema Not { get => this.schema.Not; set => this.schema.Not = value; } - public AsyncApiSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } + public AsyncApiJsonSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } - public AsyncApiSchema If { get => this.schema.If; set => this.schema.If = value; } + public AsyncApiJsonSchema If { get => this.schema.If; set => this.schema.If = value; } - public AsyncApiSchema Then { get => this.schema.Then; set => this.schema.Then = value; } + public AsyncApiJsonSchema Then { get => this.schema.Then; set => this.schema.Then = value; } - public AsyncApiSchema Else { get => this.schema.Else; set => this.schema.Else = value; } + public AsyncApiJsonSchema Else { get => this.schema.Else; set => this.schema.Else = value; } public ISet Required { get => this.schema.Required; set => this.schema.Required = value; } - public AsyncApiSchema Items { get => this.schema.Items; set => this.schema.Items = value; } + public AsyncApiJsonSchema Items { get => this.schema.Items; set => this.schema.Items = value; } - public AsyncApiSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; } + public AsyncApiJsonSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; } public int? MaxItems { get => this.schema.MaxItems; set => this.schema.MaxItems = value; } @@ -78,15 +78,15 @@ public AsyncApiJsonSchemaPayload(AsyncApiSchema schema) public bool? UniqueItems { get => this.schema.UniqueItems; set => this.schema.UniqueItems = value; } - public IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; } + public IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; } public int? MaxProperties { get => this.schema.MaxProperties; set => this.schema.MaxProperties = value; } public int? MinProperties { get => this.schema.MinProperties; set => this.schema.MinProperties = value; } - public IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; } + public IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; } - public AsyncApiSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; } + public AsyncApiJsonSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; } public string Discriminator { get => this.schema.Discriminator; set => this.schema.Discriminator = value; } @@ -108,11 +108,11 @@ public AsyncApiJsonSchemaPayload(AsyncApiSchema schema) public IDictionary Extensions { get => this.schema.Extensions; set => this.schema.Extensions = value; } - public AsyncApiSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } + public AsyncApiJsonSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } - public static implicit operator AsyncApiSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; + public static implicit operator AsyncApiJsonSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; - public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiSchema schema) => new AsyncApiJsonSchemaPayload(schema); + public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) => new AsyncApiJsonSchemaPayload(schema); public void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/JsonSchema/FalseApiSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/FalseApiSchema.cs index 01f313e5..669d8499 100644 --- a/src/LEGO.AsyncAPI/Models/JsonSchema/FalseApiSchema.cs +++ b/src/LEGO.AsyncAPI/Models/JsonSchema/FalseApiSchema.cs @@ -5,8 +5,8 @@ namespace LEGO.AsyncAPI.Models /// /// An object representing 'false' for properties of AsyncApiSchema that can be false OR a schema. /// - /// - public class FalseApiSchema : AsyncApiSchema + /// + public class FalseApiSchema : AsyncApiJsonSchema { } } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index ebf9b52b..cc3f12ed 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -87,16 +87,12 @@ public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); + // #ToFix Resolve references correctly if (message.Payload is AsyncApiJsonSchemaPayload) { this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); } - if (message.Payload is AsyncApiAvroSchemaPayload) - { - this.ResolveObject(message.Payload as AsyncApiAvroSchemaPayload, r => message.Payload = r); - } - this.ResolveList(message.Traits); this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); this.ResolveObject(message.Bindings, r => message.Bindings = r); @@ -147,7 +143,7 @@ public override void Visit(AsyncApiParameter parameter) /// /// Resolve all references used in a schema. /// - public override void Visit(AsyncApiSchema schema) + public override void Visit(AsyncApiJsonSchema schema) { this.ResolveObject(schema.Items, r => schema.Items = r); this.ResolveList(schema.OneOf); diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs index 8e3dc241..c986cfcf 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -145,9 +145,9 @@ public virtual void Visit(AsyncApiExternalDocumentation externalDocs) } /// - /// Visits . + /// Visits . /// - public virtual void Visit(AsyncApiSchema schema) + public virtual void Visit(AsyncApiJsonSchema schema) { } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index a2108647..6007181e 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -10,7 +10,7 @@ namespace LEGO.AsyncAPI.Services public class AsyncApiWalker { private readonly AsyncApiVisitorBase visitor; - private readonly Stack schemaLoop = new(); + private readonly Stack schemaLoop = new(); public AsyncApiWalker(AsyncApiVisitorBase visitor) { @@ -293,7 +293,7 @@ internal void Walk(AsyncApiParameter parameter, bool isComponent = false) this.Walk(parameter as IAsyncApiExtensible); } - internal void Walk(AsyncApiSchema schema, bool isComponent = false) + internal void Walk(AsyncApiJsonSchema schema, bool isComponent = false) { if (schema == null || this.ProcessAsReference(schema, isComponent)) { @@ -503,7 +503,7 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) this.Walk(AsyncApiConstants.Headers, () => this.Walk(message.Headers)); if (message.Payload is AsyncApiJsonSchemaPayload payload) { - this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiSchema)payload)); + this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiJsonSchema)payload)); } // #ToFix Add walking for avro. @@ -938,7 +938,7 @@ public void Walk(IAsyncApiElement element) case AsyncApiOAuthFlow e: this.Walk(e); break; case AsyncApiOperation e: this.Walk(e); break; case AsyncApiParameter e: this.Walk(e); break; - case AsyncApiSchema e: this.Walk(e); break; + case AsyncApiJsonSchema e: this.Walk(e); break; case AsyncApiSecurityRequirement e: this.Walk(e); break; case AsyncApiSecurityScheme e: this.Walk(e); break; case AsyncApiServer e: this.Walk(e); break; diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index 4ce96627..a8b2accd 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -125,10 +125,10 @@ public void AddWarning(AsyncApiValidatorWarning warning) public override void Visit(AsyncApiParameter item) => this.Validate(item); /// - /// Execute validation rules against an . + /// Execute validation rules against an . /// /// The object to be validated. - public override void Visit(AsyncApiSchema item) => this.Validate(item); + public override void Visit(AsyncApiJsonSchema item) => this.Validate(item); /// /// Execute validation rules against an . diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs index f800a1f7..2967fd8c 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs @@ -46,7 +46,7 @@ public AsyncApiDocumentBuilder WithChannel(string key, AsyncApiChannel channel) return this; } - public AsyncApiDocumentBuilder WithComponent(string key, AsyncApiSchema schema) + public AsyncApiDocumentBuilder WithComponent(string key, AsyncApiJsonSchema schema) { if (this.document.Components == null) { diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 4cbf1bd4..a6aac3c8 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -551,13 +551,13 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }) - .WithComponent("lightMeasuredPayload", new AsyncApiSchema() + .WithComponent("lightMeasuredPayload", new AsyncApiJsonSchema() { Type = SchemaType.Object, - Properties = new Dictionary() + Properties = new Dictionary() { { - "lumens", new AsyncApiSchema() + "lumens", new AsyncApiJsonSchema() { Type = SchemaType.Integer, Minimum = 0, @@ -565,7 +565,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiSchema() + "sentAt", new AsyncApiJsonSchema() { Reference = new AsyncApiReference() { @@ -576,13 +576,13 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }) - .WithComponent("turnOnOffPayload", new AsyncApiSchema() + .WithComponent("turnOnOffPayload", new AsyncApiJsonSchema() { Type = SchemaType.Object, - Properties = new Dictionary() + Properties = new Dictionary() { { - "command", new AsyncApiSchema() + "command", new AsyncApiJsonSchema() { Type = SchemaType.String, Enum = new List @@ -594,7 +594,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiSchema() + "sentAt", new AsyncApiJsonSchema() { Reference = new AsyncApiReference() { @@ -605,13 +605,13 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }) - .WithComponent("dimLightPayload", new AsyncApiSchema() + .WithComponent("dimLightPayload", new AsyncApiJsonSchema() { Type = SchemaType.Object, - Properties = new Dictionary() + Properties = new Dictionary() { { - "percentage", new AsyncApiSchema() + "percentage", new AsyncApiJsonSchema() { Type = SchemaType.Integer, Description = "Percentage to which the light should be dimmed to.", @@ -620,7 +620,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiSchema() + "sentAt", new AsyncApiJsonSchema() { Reference = new AsyncApiReference() { @@ -631,7 +631,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }) - .WithComponent("sentAt", new AsyncApiSchema() + .WithComponent("sentAt", new AsyncApiJsonSchema() { Type = SchemaType.String, Format = "date-time", @@ -650,20 +650,20 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() .WithComponent("streetlightId", new AsyncApiParameter() { Description = "The ID of the streetlight.", - Schema = new AsyncApiSchema() + Schema = new AsyncApiJsonSchema() { Type = SchemaType.String, }, }) .WithComponent("commonHeaders", new AsyncApiMessageTrait() { - Headers = new AsyncApiSchema() + Headers = new AsyncApiJsonSchema() { Type = SchemaType.Object, - Properties = new Dictionary() + Properties = new Dictionary() { { - "my-app-header", new AsyncApiSchema() + "my-app-header", new AsyncApiJsonSchema() { Type = SchemaType.Integer, Minimum = 0, @@ -680,7 +680,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { "kafka", new KafkaOperationBinding() { - ClientId = new AsyncApiSchema() + ClientId = new AsyncApiJsonSchema() { Type = SchemaType.String, Enum = new List @@ -1016,7 +1016,7 @@ public void SerializeV2_WithFullSpec_Serializes() { Name = traitName, Title = traitTitle, - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = schemaTitle, WriteOnly = true, @@ -1285,7 +1285,7 @@ public void Serializev2_WithBindings_Serializes() { new HttpMessageBinding { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Description = "this mah binding", }, @@ -1294,7 +1294,7 @@ public void Serializev2_WithBindings_Serializes() { new KafkaMessageBinding { - Key = new AsyncApiSchema + Key = new AsyncApiJsonSchema { Description = "this mah other binding", }, diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Http/HttpBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Http/HttpBindings_Should.cs index f2c17c6c..08cd1bee 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Http/HttpBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Http/HttpBindings_Should.cs @@ -27,7 +27,7 @@ public void HttpMessageBinding_FilledObject_SerializesAndDeserializes() message.Bindings.Add(new HttpMessageBinding { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Description = "this mah binding", }, @@ -65,7 +65,7 @@ public void HttpOperationBinding_FilledObject_SerializesAndDeserializes() { Type = HttpOperationBinding.HttpOperationType.Request, Method = "POST", - Query = new AsyncApiSchema + Query = new AsyncApiJsonSchema { Description = "this mah query", }, diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs index 2c5c6f3f..828acedc 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs @@ -128,7 +128,7 @@ public void KafkaMessageBinding_WithFilledObject_SerializesAndDeserializes() message.Bindings.Add(new KafkaMessageBinding { - Key = new AsyncApiSchema + Key = new AsyncApiJsonSchema { Description = "this mah other binding", }, @@ -167,11 +167,11 @@ public void KafkaOperationBinding_WithFilledObject_SerializesAndDeserializes() operation.Bindings.Add(new KafkaOperationBinding { - GroupId = new AsyncApiSchema + GroupId = new AsyncApiJsonSchema { Description = "this mah groupId", }, - ClientId = new AsyncApiSchema + ClientId = new AsyncApiJsonSchema { Description = "this mah clientId", }, diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs index b8aef976..ecab039f 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs @@ -30,11 +30,11 @@ public void WebSocketChannelBinding_WithFilledObject_SerializesAndDeserializes() channel.Bindings.Add(new WebSocketsChannelBinding { Method = "POST", - Query = new AsyncApiSchema + Query = new AsyncApiJsonSchema { Description = "this mah query", }, - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Description = "this mah binding", }, diff --git a/test/LEGO.AsyncAPI.Tests/MQTT/MQTTBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/MQTT/MQTTBindings_Should.cs index bbccf86e..2424c21c 100644 --- a/test/LEGO.AsyncAPI.Tests/MQTT/MQTTBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/MQTT/MQTTBindings_Should.cs @@ -119,7 +119,7 @@ public void MQTTMessageBinding_WithFilledObject_SerializesAndDeserializes() message.Bindings.Add(new MQTTMessageBinding { ContentType = "application/json", - CorrelationData = new AsyncApiSchema + CorrelationData = new AsyncApiJsonSchema { Type = SchemaType.String, Format = "uuid", diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index b19cd383..4fbc2512 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs @@ -59,24 +59,24 @@ public void AsyncApiChannel_WithWebSocketsBinding_Serializes() new WebSocketsChannelBinding() { Method = "POST", - Query = new AsyncApiSchema() + Query = new AsyncApiJsonSchema() { - Properties = new Dictionary() + Properties = new Dictionary() { { - "index", new AsyncApiSchema() + "index", new AsyncApiJsonSchema() { Description = "the index", } }, }, }, - Headers = new AsyncApiSchema() + Headers = new AsyncApiJsonSchema() { - Properties = new Dictionary() + Properties = new Dictionary() { { - "x-correlation-id", new AsyncApiSchema() + "x-correlation-id", new AsyncApiJsonSchema() { Description = "the correlationid", } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index 487b60e5..cb97179d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -106,10 +106,10 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() var message = new AsyncApiMessage(); message.Payload = new AsyncApiJsonSchemaPayload() { - Properties = new Dictionary() + Properties = new Dictionary() { { - "propertyA", new AsyncApiSchema() + "propertyA", new AsyncApiJsonSchema() { Type = SchemaType.String | SchemaType.Null, } @@ -147,10 +147,10 @@ public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() message.SchemaFormat = "application/vnd.aai.asyncapi+json;version=2.6.0"; message.Payload = new AsyncApiJsonSchemaPayload() { - Properties = new Dictionary() + Properties = new Dictionary() { { - "propertyA", new AsyncApiSchema() + "propertyA", new AsyncApiJsonSchema() { Type = SchemaType.String | SchemaType.Null, } @@ -316,7 +316,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() var message = new AsyncApiMessage { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = "HeaderTitle", WriteOnly = true, @@ -331,16 +331,16 @@ public void AsyncApiMessage_WithFilledObject_Serializes() }, Payload = new AsyncApiJsonSchemaPayload() { - Properties = new Dictionary + Properties = new Dictionary { { - "propA", new AsyncApiSchema() + "propA", new AsyncApiJsonSchema() { Type = SchemaType.String, } }, { - "propB", new AsyncApiSchema() + "propB", new AsyncApiJsonSchema() { Type = SchemaType.String, } @@ -379,7 +379,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() { "http", new HttpMessageBinding { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = "SchemaTitle", WriteOnly = true, @@ -413,7 +413,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() { Name = "MessageTraitName", Title = "MessageTraitTitle", - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = "SchemaTitle", WriteOnly = true, diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs index 847da7be..6c53ad3d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs @@ -105,7 +105,7 @@ public void AsyncApiOperation_WithBindings_Serializes() { Type = HttpOperationBinding.HttpOperationType.Request, Method = "PUT", - Query = new AsyncApiSchema + Query = new AsyncApiJsonSchema { Description = "some query", }, @@ -114,11 +114,11 @@ public void AsyncApiOperation_WithBindings_Serializes() { new KafkaOperationBinding { - GroupId = new AsyncApiSchema + GroupId = new AsyncApiJsonSchema { Description = "some Id", }, - ClientId = new AsyncApiSchema + ClientId = new AsyncApiJsonSchema { Description = "some Id", }, diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 53fc0350..65c6aada 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -13,9 +13,9 @@ namespace LEGO.AsyncAPI.Tests.Models public class AsyncApiSchema_Should : TestBase { - public static AsyncApiSchema BasicSchema = new AsyncApiSchema(); + public static AsyncApiJsonSchema BasicSchema = new AsyncApiJsonSchema(); - public static AsyncApiSchema AdvancedSchemaNumber = new AsyncApiSchema + public static AsyncApiJsonSchema AdvancedSchemaNumber = new AsyncApiJsonSchema { Title = "title1", MultipleOf = 3, @@ -31,7 +31,7 @@ public class AsyncApiSchema_Should : TestBase }, }; - public static AsyncApiSchema AdvancedSchemaBigNumbers = new AsyncApiSchema + public static AsyncApiJsonSchema AdvancedSchemaBigNumbers = new AsyncApiJsonSchema { Title = "title1", MultipleOf = 3, @@ -47,20 +47,20 @@ public class AsyncApiSchema_Should : TestBase }, }; - public static AsyncApiSchema AdvancedSchemaObject = new AsyncApiSchema + public static AsyncApiJsonSchema AdvancedSchemaObject = new AsyncApiJsonSchema { Title = "title1", - Properties = new Dictionary + Properties = new Dictionary { - ["property1"] = new AsyncApiSchema + ["property1"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property2"] = new AsyncApiSchema + ["property2"] = new AsyncApiJsonSchema { Type = SchemaType.Integer, }, - ["property3"] = new AsyncApiSchema + ["property3"] = new AsyncApiJsonSchema { Type = SchemaType.String, MaxLength = 15, @@ -70,78 +70,78 @@ public class AsyncApiSchema_Should : TestBase Items = new FalseApiSchema(), AdditionalItems = new FalseApiSchema(), }, - ["property4"] = new AsyncApiSchema + ["property4"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property5"] = new AsyncApiSchema + ["property5"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property6"] = new AsyncApiSchema + ["property6"] = new AsyncApiJsonSchema { Type = SchemaType.Boolean, }, }, }, - ["property7"] = new AsyncApiSchema + ["property7"] = new AsyncApiJsonSchema { Type = SchemaType.String, MinLength = 2, }, }, - PatternProperties = new Dictionary() + PatternProperties = new Dictionary() { { "^S_", - new AsyncApiSchema() + new AsyncApiJsonSchema() { Type = SchemaType.String, } }, { - "^I_", new AsyncApiSchema() + "^I_", new AsyncApiJsonSchema() { Type = SchemaType.Integer, } }, }, - PropertyNames = new AsyncApiSchema() + PropertyNames = new AsyncApiJsonSchema() { Pattern = "^[A-Za-z_][A-Za-z0-9_]*$", }, - AdditionalProperties = new AsyncApiSchema + AdditionalProperties = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["Property8"] = new AsyncApiSchema + ["Property8"] = new AsyncApiJsonSchema { Type = SchemaType.String | SchemaType.Null, }, }, }, - Items = new AsyncApiSchema + Items = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["Property9"] = new AsyncApiSchema + ["Property9"] = new AsyncApiJsonSchema { Type = SchemaType.String | SchemaType.Null, }, }, }, - AdditionalItems = new AsyncApiSchema + AdditionalItems = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["Property10"] = new AsyncApiSchema + ["Property10"] = new AsyncApiJsonSchema { Type = SchemaType.String | SchemaType.Null, }, }, }, }, - ["property11"] = new AsyncApiSchema + ["property11"] = new AsyncApiJsonSchema { Const = new AsyncApiAny("aSpecialConstant"), }, @@ -153,43 +153,43 @@ public class AsyncApiSchema_Should : TestBase }, }; - public static AsyncApiSchema AdvancedSchemaWithAllOf = new AsyncApiSchema + public static AsyncApiJsonSchema AdvancedSchemaWithAllOf = new AsyncApiJsonSchema { Title = "title1", - AllOf = new List + AllOf = new List { - new AsyncApiSchema + new AsyncApiJsonSchema { Title = "title2", - Properties = new Dictionary + Properties = new Dictionary { - ["property1"] = new AsyncApiSchema + ["property1"] = new AsyncApiJsonSchema { Type = SchemaType.Integer, }, - ["property2"] = new AsyncApiSchema + ["property2"] = new AsyncApiJsonSchema { Type = SchemaType.String, MaxLength = 15, }, }, }, - new AsyncApiSchema + new AsyncApiJsonSchema { Title = "title3", - Properties = new Dictionary + Properties = new Dictionary { - ["property3"] = new AsyncApiSchema + ["property3"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property4"] = new AsyncApiSchema + ["property4"] = new AsyncApiJsonSchema { Type = SchemaType.Boolean , }, }, }, - ["property5"] = new AsyncApiSchema + ["property5"] = new AsyncApiJsonSchema { Type = SchemaType.String, MinLength = 2, @@ -205,7 +205,7 @@ public class AsyncApiSchema_Should : TestBase }, }; - public static AsyncApiSchema ReferencedSchema = new AsyncApiSchema + public static AsyncApiJsonSchema ReferencedSchema = new AsyncApiJsonSchema { Title = "title1", MultipleOf = 3, @@ -228,22 +228,22 @@ public class AsyncApiSchema_Should : TestBase }, }; - public static AsyncApiSchema AdvancedSchemaWithRequiredPropertiesObject = new AsyncApiSchema + public static AsyncApiJsonSchema AdvancedSchemaWithRequiredPropertiesObject = new AsyncApiJsonSchema { Title = "title1", Required = new HashSet() { "property1" }, - Properties = new Dictionary + Properties = new Dictionary { - ["property1"] = new AsyncApiSchema + ["property1"] = new AsyncApiJsonSchema { Required = new HashSet() { "property3" }, - Properties = new Dictionary + Properties = new Dictionary { - ["property2"] = new AsyncApiSchema + ["property2"] = new AsyncApiJsonSchema { Type = SchemaType.Integer, }, - ["property3"] = new AsyncApiSchema + ["property3"] = new AsyncApiJsonSchema { Type = SchemaType.String, MaxLength = 15, @@ -252,21 +252,21 @@ public class AsyncApiSchema_Should : TestBase }, ReadOnly = true, }, - ["property4"] = new AsyncApiSchema + ["property4"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property5"] = new AsyncApiSchema + ["property5"] = new AsyncApiJsonSchema { - Properties = new Dictionary + Properties = new Dictionary { - ["property6"] = new AsyncApiSchema + ["property6"] = new AsyncApiJsonSchema { Type = SchemaType.Boolean, }, }, }, - ["property7"] = new AsyncApiSchema + ["property7"] = new AsyncApiJsonSchema { Type = SchemaType.String, MinLength = 2, @@ -374,7 +374,7 @@ public void Deserialize_WithAdvancedSchema_Works() var expected = AdvancedSchemaObject; // Act - var actual = new AsyncApiStringReader().ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _diagnostics); + var actual = new AsyncApiStringReader().ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _diagnostics); // Assert actual.Should().BeEquivalentTo(expected); @@ -424,26 +424,26 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl { Type = SchemaType.Object, Required = new HashSet { "testB" }, - Properties = new Dictionary + Properties = new Dictionary { - { "testC", new AsyncApiSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testC" } } }, - { "testB", new AsyncApiSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testB" } } }, + { "testC", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testC" } } }, + { "testB", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testB" } } }, }, }, }, }, }, }) - .WithComponent("testD", new AsyncApiSchema() { Type = SchemaType.String, Format = "uuid" }) - .WithComponent("testC", new AsyncApiSchema() + .WithComponent("testD", new AsyncApiJsonSchema() { Type = SchemaType.String, Format = "uuid" }) + .WithComponent("testC", new AsyncApiJsonSchema() { Type = SchemaType.Object, - Properties = new Dictionary + Properties = new Dictionary { - { "testD", new AsyncApiSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testD" } } }, + { "testD", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testD" } } }, }, }) - .WithComponent("testB", new AsyncApiSchema() { Description = "test", Type = SchemaType.Boolean }) + .WithComponent("testB", new AsyncApiJsonSchema() { Description = "test", Type = SchemaType.Boolean }) .Build(); var outputString = new StringWriter(); @@ -481,10 +481,10 @@ public void SerializeV2_WithNullWriter_Throws() [Test] public void Serialize_WithOneOf_DoesNotWriteThen() { - var mainSchema = new AsyncApiSchema(); - var subSchema = new AsyncApiSchema(); - subSchema.Properties.Add("title", new AsyncApiSchema() { Type = SchemaType.String }); - mainSchema.OneOf = new List() { subSchema }; + var mainSchema = new AsyncApiJsonSchema(); + var subSchema = new AsyncApiJsonSchema(); + subSchema.Properties.Add("title", new AsyncApiJsonSchema() { Type = SchemaType.String }); + mainSchema.OneOf = new List() { subSchema }; var yaml = mainSchema.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); @@ -499,10 +499,10 @@ public void Serialize_WithOneOf_DoesNotWriteThen() [Test] public void Serialize_WithAnyOf_DoesNotWriteIf() { - var mainSchema = new AsyncApiSchema(); - var subSchema = new AsyncApiSchema(); - subSchema.Properties.Add("title", new AsyncApiSchema() { Type = SchemaType.String }); - mainSchema.AnyOf = new List() { subSchema }; + var mainSchema = new AsyncApiJsonSchema(); + var subSchema = new AsyncApiJsonSchema(); + subSchema.Properties.Add("title", new AsyncApiJsonSchema() { Type = SchemaType.String }); + mainSchema.AnyOf = new List() { subSchema }; var yaml = mainSchema.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); @@ -526,7 +526,7 @@ public void Deserialize_BasicExample() url: http://example.com/externalDocs """; - var schema = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + var schema = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); diag.Errors.Should().BeEmpty(); schema.Should().BeEquivalentTo(AdvancedSchemaNumber); @@ -538,9 +538,9 @@ public void Deserialize_BasicExample() [Test] public void Serialize_WithNot_DoesNotWriteElse() { - var mainSchema = new AsyncApiSchema(); - var subSchema = new AsyncApiSchema(); - subSchema.Properties.Add("title", new AsyncApiSchema() { Type = SchemaType.String }); + var mainSchema = new AsyncApiJsonSchema(); + var subSchema = new AsyncApiJsonSchema(); + subSchema.Properties.Add("title", new AsyncApiJsonSchema() { Type = SchemaType.String }); mainSchema.Not = subSchema; var yaml = mainSchema.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); From 28d38807043393214e9667de00588602ee5f2081 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 23 Jan 2025 11:31:05 +0100 Subject: [PATCH 03/20] more reference work --- .../AsyncApiJsonDocumentReader.cs | 4 +- .../ParseNodes/MapNode.cs | 1 - src/LEGO.AsyncAPI.Readers/ParsingContext.cs | 4 + .../V2/AsyncApiComponentsDeserializer.cs | 2 +- .../V2/AsyncApiServerDeserializer.cs | 2 +- .../V2/AsyncApiV2VersionService.cs | 90 +--- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 138 +++++- .../Models/AsyncApiComponents.cs | 6 +- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 149 +++---- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 99 ++++- src/LEGO.AsyncAPI/Models/AsyncApiServer.cs | 46 +- .../Interfaces/IAsyncApiReferenceable.cs | 10 - .../References/AsyncApiServerReference.cs | 64 +++ .../Services/AsyncApiReferenceResolver.cs | 412 +++++++++--------- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 3 +- .../Validation/AsyncApiValidator.cs | 2 +- .../Writers/AsyncApiYamlWriter.cs | 10 - .../AsyncApiDocumentV2Tests.cs | 2 +- .../Models/AsyncApiMessage_Should.cs | 2 +- .../Models/AsyncApiReference_Should.cs | 58 +-- test/LEGO.AsyncAPI.Tests/ReferenceTests.cs | 72 +++ 21 files changed, 688 insertions(+), 488 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs create mode 100644 test/LEGO.AsyncAPI.Tests/ReferenceTests.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index c1664c49..e66ed58c 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -236,8 +236,8 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo case ReferenceType.Schema: result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); break; - case ReferenceType.Server: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + //case ReferenceType.Server: + // result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); break; case ReferenceType.Channel: result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs index 63bea6fc..76a98a5d 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs @@ -185,7 +185,6 @@ public T GetReferencedObject(ReferenceType referenceType, string referenceId) { return new T() { - UnresolvedReference = true, Reference = this.Context.VersionService.ConvertToAsyncApiReference(referenceId, referenceType), }; } diff --git a/src/LEGO.AsyncAPI.Readers/ParsingContext.cs b/src/LEGO.AsyncAPI.Readers/ParsingContext.cs index 9bcc051b..22588edf 100644 --- a/src/LEGO.AsyncAPI.Readers/ParsingContext.cs +++ b/src/LEGO.AsyncAPI.Readers/ParsingContext.cs @@ -44,6 +44,8 @@ internal Dictionary> ExtensionPars /// public AsyncApiReaderSettings Settings { get; } + public AsyncApiWorkspace Workspace { get; } + ///// ///// Initializes a new instance of the class. ///// @@ -63,6 +65,7 @@ public ParsingContext(AsyncApiDiagnostic diagnostic, AsyncApiReaderSettings sett { this.Diagnostic = diagnostic; this.Settings = settings; + this.Workspace = new AsyncApiWorkspace(); } internal AsyncApiDocument Parse(JsonNode jsonNode) @@ -78,6 +81,7 @@ internal AsyncApiDocument Parse(JsonNode jsonNode) case string version when version.StartsWith("2"): this.VersionService = new AsyncApiV2VersionService(this.Diagnostic); doc = this.VersionService.LoadDocument(this.RootNode); + this.Workspace.RegisterComponents(doc); this.Diagnostic.SpecificationVersion = AsyncApiVersion.AsyncApi2_0; break; diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index df13308a..fea37c45 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -11,7 +11,7 @@ internal static partial class AsyncApiV2Deserializer private static FixedFieldMap componentsFixedFields = new() { { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, AsyncApiSchemaDeserializer.LoadSchema) }, - { "servers", (a, n) => a.Servers = n.CreateMapWithReference(ReferenceType.Server, LoadServer) }, + { "servers", (a, n) => a.Servers = n.CreateMap(LoadServer) }, { "channels", (a, n) => a.Channels = n.CreateMapWithReference(ReferenceType.Channel, LoadChannel) }, { "messages", (a, n) => a.Messages = n.CreateMapWithReference(ReferenceType.Message, LoadMessage) }, { "securitySchemes", (a, n) => a.SecuritySchemes = n.CreateMapWithReference(ReferenceType.SecurityScheme, LoadSecurityScheme) }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerDeserializer.cs index 6eae00af..9d72d588 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerDeserializer.cs @@ -52,7 +52,7 @@ public static AsyncApiServer LoadServer(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Server, pointer); + return new AsyncApiServerReference(pointer); } var server = new AsyncApiServer(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index c060455e..21f3ffc0 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -62,73 +62,15 @@ public AsyncApiReference ConvertToAsyncApiReference( throw new AsyncApiException($"The reference string '{reference}' has invalid format."); } - var segments = reference.Split('#'); - if (segments.Length == 1) + try { - if (type == ReferenceType.SecurityScheme) - { - return new AsyncApiReference - { - Type = type, - Id = reference, - }; - } - - var asyncApiReference = new AsyncApiReference(); - asyncApiReference.Type = type; - asyncApiReference.ExternalResource = segments[0]; - - return asyncApiReference; + return new AsyncApiReference(reference, type); } - else if (segments.Length == 2) + catch (AsyncApiException ex) { - // Local components reference - if (reference.StartsWith("#")) - { - try - { - return this.ParseReference(segments[1]); - } - catch (AsyncApiException ex) - { - this.Diagnostic.Errors.Add(new AsyncApiError(ex)); - return null; - } - } - - var asyncApiReference = new AsyncApiReference(); - var id = segments[1]; - if (id.StartsWith("/components/")) - { - var localSegments = segments[1].Split('/'); - var referencedType = localSegments[2].GetEnumFromDisplayName(); - if (type == null) - { - type = referencedType; - } - else - { - if (type != referencedType) - { - throw new AsyncApiException("Referenced type mismatch"); - } - } - - id = localSegments[3]; - } - else - { - asyncApiReference.IsFragment = true; - } - - asyncApiReference.ExternalResource = segments[0]; - asyncApiReference.Type = type; - asyncApiReference.Id = id; - - return asyncApiReference; + this.Diagnostic.Errors.Add(new AsyncApiError(ex)); + return null; } - - throw new AsyncApiException($"The reference string '{reference}' has invalid format."); } public AsyncApiDocument LoadDocument(RootNode rootNode) @@ -141,27 +83,5 @@ public T LoadElement(ParseNode node) { return (T)this.loaders[typeof(T)](node); } - - private AsyncApiReference ParseReference(string localReference) - { - if (string.IsNullOrWhiteSpace(localReference)) - { - throw new ArgumentException( - $"The argument '{nameof(localReference)}' is null, empty or consists only of white-space."); - } - - var segments = localReference.Split('/'); - - if (segments.Length == 4) - { - if (segments[1] == "components") - { - var referenceType = segments[2].GetEnumFromDisplayName(); - return new AsyncApiReference { Type = referenceType, Id = segments[3] }; - } - } - - throw new AsyncApiException($"The reference string '{localReference}' has invalid format."); - } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index 64ca1c94..b52985ca 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -12,19 +12,150 @@ public class AsyncApiWorkspace { private readonly Dictionary documentsIdRegistry = new(); private readonly Dictionary artifactsRegistry = new(); - private readonly Dictionary asyncApiReferenceableRegistry = new(); + private readonly Dictionary asyncApiReferenceableRegistry = new(); public Uri BaseUrl { get; } public AsyncApiWorkspace() { - this.BaseUrl = new Uri(AsyncApiConstants.BaseRegistryUri); + this.BaseUrl = new Uri("http://asyncapi.net"); + } + + public AsyncApiWorkspace(Uri baseUrl) + { + this.BaseUrl = baseUrl; + } + + public void RegisterComponents(AsyncApiDocument document) + { + document.Workspace = this; + if (document?.Components == null) + { + return; + } + + string baseUri = "#/components/"; + string location; + + // Register Schema + foreach (var item in document.Components.Schemas) + { + location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register Parameters + foreach (var item in document.Components.Parameters) + { + location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register Channels + foreach (var item in document.Components.Channels) + { + location = baseUri + ReferenceType.Channel.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register Servers + foreach (var item in document.Components.Servers) + { + location = baseUri + ReferenceType.Server.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register ServerVariables + foreach (var item in document.Components.ServerVariables) + { + location = baseUri + ReferenceType.ServerVariable.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register Messages + foreach (var item in document.Components.Messages) + { + location = baseUri + ReferenceType.Message.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register SecuritySchemes + foreach (var item in document.Components.SecuritySchemes) + { + location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register Parameters + foreach (var item in document.Components.Parameters) + { + location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register CorrelationIds + foreach (var item in document.Components.CorrelationIds) + { + location = baseUri + ReferenceType.CorrelationId.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register OperationTraits + foreach (var item in document.Components.OperationTraits) + { + location = baseUri + ReferenceType.OperationTrait.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register MessageTraits + foreach (var item in document.Components.MessageTraits) + { + location = baseUri + ReferenceType.MessageTrait.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register ServerBindings + foreach (var item in document.Components.ServerBindings) + { + location = baseUri + ReferenceType.ServerBindings.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register ChannelBindings + foreach (var item in document.Components.ChannelBindings) + { + location = baseUri + ReferenceType.ChannelBindings.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register OperationBindings + foreach (var item in document.Components.OperationBindings) + { + location = baseUri + ReferenceType.OperationBindings.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + + // Register MessageBindings + foreach (var item in document.Components.MessageBindings) + { + location = baseUri + ReferenceType.MessageBindings.GetDisplayName() + "/" + item.Key; + this.RegisterComponent(location, item.Value); + } + } + + private void RegisterInternalComponent(string location, IAsyncApiReferenceable component) + { + var uri = this.ToLocationUrl(location); + if (!this.asyncApiReferenceableRegistry.ContainsKey(uri)) + { + this.asyncApiReferenceableRegistry[uri] = component; + } } public bool RegisterComponent(string location, T component) { var uri = this.ToLocationUrl(location); - if (component is IAsyncApiReferenceable referenceable) + if (component is IAsyncApiSerializable referenceable) { if (!this.asyncApiReferenceableRegistry.ContainsKey(uri)) { @@ -58,6 +189,7 @@ public Uri GetDocumentId(string key) { return id; } + return null; } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index b64f5a66..54412d52 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -148,11 +148,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.Servers, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Server && - component.Reference.Id == key) + if (component is AsyncApiServerReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 5be55202..d3a1bb1c 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Models { using System; using System.Collections.Generic; - using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; using LEGO.AsyncAPI.Services; @@ -14,6 +13,8 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable { + internal AsyncApiWorkspace Workspace { get; set; } + /// /// REQUIRED. Specifies the AsyncAPI Specification version being used. /// @@ -90,37 +91,13 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteOptionalProperty(AsyncApiConstants.Id, this.Id); // servers - writer.WriteOptionalMap(AsyncApiConstants.Servers, this.Servers, (writer, key, component) => - { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Server && - component.Reference.Id == key) - { - component.SerializeV2WithoutReference(writer); - } - else - { - component.SerializeV2(writer); - } - }); + writer.WriteOptionalMap(AsyncApiConstants.Servers, this.Servers, (writer, key, component) => component.SerializeV2(writer)); // content type writer.WriteOptionalProperty(AsyncApiConstants.DefaultContentType, this.DefaultContentType); // channels - writer.WriteRequiredMap(AsyncApiConstants.Channels, this.Channels, (writer, key, component) => - { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Channel && - component.Reference.Id == key) - { - component.SerializeV2WithoutReference(writer); - } - else - { - component.SerializeV2(writer); - } - }); + writer.WriteRequiredMap(AsyncApiConstants.Channels, this.Channels, (writer, key, component) => component.SerializeV2(writer)); // components writer.WriteOptionalObject(AsyncApiConstants.Components, this.Components, (w, c) => c.SerializeV2(w)); @@ -145,67 +122,67 @@ public IEnumerable ResolveReferences() return resolver.Errors; } - internal T ResolveReference(AsyncApiReference reference) - where T : class, IAsyncApiReferenceable + internal T? ResolveReference(AsyncApiReference reference) + where T : class { - return this.ResolveReference(reference) as T; + return this.Workspace.ResolveReference(reference.Reference); } - internal IAsyncApiReferenceable ResolveReference(AsyncApiReference reference) - { - if (reference == null) - { - return null; - } - - if (!reference.Type.HasValue) - { - throw new ArgumentException("Reference must have a type."); - } - - if (this.Components == null) - { - throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); - } - - try - { - switch (reference.Type) - { - case ReferenceType.Schema: - return this.Components.Schemas[reference.Id]; - case ReferenceType.Server: - return this.Components.Servers[reference.Id]; - case ReferenceType.Channel: - return this.Components.Channels[reference.Id]; - case ReferenceType.Message: - return this.Components.Messages[reference.Id]; - case ReferenceType.SecurityScheme: - return this.Components.SecuritySchemes[reference.Id]; - case ReferenceType.Parameter: - return this.Components.Parameters[reference.Id]; - case ReferenceType.CorrelationId: - return this.Components.CorrelationIds[reference.Id]; - case ReferenceType.OperationTrait: - return this.Components.OperationTraits[reference.Id]; - case ReferenceType.MessageTrait: - return this.Components.MessageTraits[reference.Id]; - case ReferenceType.ServerBindings: - return this.Components.ServerBindings[reference.Id]; - case ReferenceType.ChannelBindings: - return this.Components.ChannelBindings[reference.Id]; - case ReferenceType.OperationBindings: - return this.Components.OperationBindings[reference.Id]; - case ReferenceType.MessageBindings: - return this.Components.MessageBindings[reference.Id]; - default: - throw new AsyncApiException("Invalid reference type."); - } - } - catch (KeyNotFoundException) - { - throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); - } - } + //internal IAsyncApiReferenceable ResolveReference(AsyncApiReference reference) + //{ + // if (reference == null) + // { + // return null; + // } + + // if (!reference.Type.HasValue) + // { + // throw new ArgumentException("Reference must have a type."); + // } + + // if (this.Components == null) + // { + // throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); + // } + + // try + // { + // switch (reference.Type) + // { + // case ReferenceType.Schema: + // return this.Components.Schemas[reference.Id]; + // case ReferenceType.Server: + // return this.Components.Servers[reference.Id]; + // case ReferenceType.Channel: + // return this.Components.Channels[reference.Id]; + // case ReferenceType.Message: + // return this.Components.Messages[reference.Id]; + // case ReferenceType.SecurityScheme: + // return this.Components.SecuritySchemes[reference.Id]; + // case ReferenceType.Parameter: + // return this.Components.Parameters[reference.Id]; + // case ReferenceType.CorrelationId: + // return this.Components.CorrelationIds[reference.Id]; + // case ReferenceType.OperationTrait: + // return this.Components.OperationTraits[reference.Id]; + // case ReferenceType.MessageTrait: + // return this.Components.MessageTraits[reference.Id]; + // case ReferenceType.ServerBindings: + // return this.Components.ServerBindings[reference.Id]; + // case ReferenceType.ChannelBindings: + // return this.Components.ChannelBindings[reference.Id]; + // case ReferenceType.OperationBindings: + // return this.Components.OperationBindings[reference.Id]; + // case ReferenceType.MessageBindings: + // return this.Components.MessageBindings[reference.Id]; + // default: + // throw new AsyncApiException("Invalid reference type."); + // } + // } + // catch (KeyNotFoundException) + // { + // throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); + // } + //} } } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 9475310e..d7151e0f 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -3,6 +3,8 @@ namespace LEGO.AsyncAPI.Models { using System; + using System.Linq; + using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -11,13 +13,97 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiReference : IAsyncApiSerializable { + public AsyncApiReference() + { + } + + public AsyncApiReference(string reference, ReferenceType? type) + { + if (string.IsNullOrWhiteSpace(reference)) + { + throw new AsyncApiException($"The reference string '{reference}' has invalid format."); + } + + var segments = reference.Split('#'); + if (segments.Length == 1) + { + if (type == ReferenceType.SecurityScheme) + { + this.Type = type; + this.Id = reference; + return; + } + + this.Type = type; + this.ExternalResource = segments[0]; + + return; + } + else if (segments.Length == 2) + { + // Local components reference + if (reference.StartsWith("#")) + { + var localSegments = reference.Split('/'); + + if (localSegments.Length == 4) + { + if (localSegments[1] == "components") + { + var referenceType = localSegments[2].GetEnumFromDisplayName(); + + this.Type = referenceType; + this.Id = localSegments[3]; + this.IsFragment = true; + return; + } + } + + throw new AsyncApiException($"The reference string '{reference}' has invalid format."); + } + + var id = segments[1]; + if (id.StartsWith("/components/")) + { + var localSegments = segments[1].Split('/'); + var referencedType = localSegments[2].GetEnumFromDisplayName(); + if (type == null) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new AsyncApiException("Referenced type mismatch"); + } + } + + id = localSegments[3]; + } + + this.IsFragment = true; + this.ExternalResource = segments[0]; + this.Type = type; + this.Id = id; + + return; + } + + throw new AsyncApiException($"The reference string '{reference}' has invalid format."); + } + /// /// External resource in the reference. /// It maybe: /// 1. a absolute/relative file path, for example: ../commons/pet.json /// 2. a Url, for example: http://localhost/pet.json. /// - public string ExternalResource { get; set; } + public string ExternalResource + { + get; + set; + } /// /// Gets or sets the element type referenced. @@ -97,17 +183,8 @@ public void SerializeV2(IAsyncApiWriter writer) private string GetExternalReferenceV2() { - if (this.Id != null) - { - if (this.IsFragment) - { - return this.ExternalResource + "#" + this.Id; - } - - return this.ExternalResource + "#/components/" + this.Type.GetDisplayName() + "/" + this.Id; - } - return this.ExternalResource; + return this.ExternalResource + "/#/" + this.Id ?? string.Empty; } public void Write(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs index 5d4fa071..d977d83e 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs @@ -6,36 +6,32 @@ namespace LEGO.AsyncAPI.Models using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - - /// - /// The definition of a server this application MAY connect to. - /// - public class AsyncApiServer : IAsyncApiSerializable, IAsyncApiExtensible, IAsyncApiReferenceable + public class AsyncApiServer : IAsyncApiSerializable, IAsyncApiExtensible { /// /// REQUIRED. A URL to the target host. /// - public string Url { get; set; } + public virtual string Url { get; set; } /// /// REQUIRED. The protocol this URL supports for connection. /// - public string Protocol { get; set; } + public virtual string Protocol { get; set; } /// /// the version of the protocol used for connection. /// - public string ProtocolVersion { get; set; } + public virtual string ProtocolVersion { get; set; } /// /// an optional string describing the host designated by the URL. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// a map between a variable name and its value. The value is used for substitution in the server's URL template. /// - public IDictionary Variables { get; set; } = new Dictionary(); + public virtual IDictionary Variables { get; set; } = new Dictionary(); /// /// a declaration of which security mechanisms can be used with this server. The list of values includes alternative security requirement objects that can be used. @@ -43,42 +39,22 @@ public class AsyncApiServer : IAsyncApiSerializable, IAsyncApiExtensible, IAsync /// /// The name used for each property MUST correspond to a security scheme declared in the Security Schemes under the Components Object. /// - public IList Security { get; set; } = new List(); + public virtual IList Security { get; set; } = new List(); /// /// A list of tags for logical grouping and categorization of servers. /// - public IList Tags { get; set; } = new List(); + public virtual IList Tags { get; set; } = new List(); /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the server. /// - public AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); + public virtual AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); - - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs index 9f0bc64c..0db58a90 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs @@ -6,19 +6,9 @@ namespace LEGO.AsyncAPI.Models.Interfaces public interface IAsyncApiReferenceable : IAsyncApiSerializable { - /// - /// Indicates if object is populated with data or is just a reference to the data. - /// - bool UnresolvedReference { get; set; } - /// /// Reference object. /// AsyncApiReference Reference { get; set; } - - /// - /// Serialize to AsyncAPI V2 document without using reference. - /// - void SerializeV2WithoutReference(IAsyncApiWriter writer); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs new file mode 100644 index 00000000..2392e1ef --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -0,0 +1,64 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a server this application MAY connect to. + /// + public class AsyncApiServerReference : AsyncApiServer, IAsyncApiReferenceable + { + private AsyncApiServer target; + + private AsyncApiServer Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiServerReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Server); + } + + public override string Url { get => this.Target.Url; set => this.Target.Url = value; } + + public override string Protocol { get => this.Target.Protocol; set => this.Target.Protocol = value; } + + public override string ProtocolVersion { get => this.Target.ProtocolVersion; set => this.Target.ProtocolVersion = value; } + + public override string Description { get => this.Target.Description; set => this.Target.Description = value; } + + public override IDictionary Variables { get => this.Target.Variables; set => this.Target.Variables = value; } + + public override IList Security { get => this.Target.Security; set => this.Target.Security = value; } + + public override IList Tags { get => this.Target.Tags; set => this.Target.Tags = value; } + + public override AsyncApiBindings Bindings { get => this.Target.Bindings; set => this.Target.Bindings = value; } + + public override IDictionary Extensions { get => this.Target.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index cc3f12ed..17228226 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -38,211 +38,211 @@ public override void Visit(IAsyncApiReferenceable referenceable) } } - 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); - } - - /// - /// Resolve all references used in an operation. - /// - 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); - - // #ToFix Resolve references correctly - if (message.Payload is AsyncApiJsonSchemaPayload) - { - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); - } - - /// - /// Resolve all references to SecuritySchemes. - /// - 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); - } - }); - } - } - - /// - /// Resolve all references to parameters. - /// - public override void Visit(IList parameters) - { - this.ResolveList(parameters); - } - - /// - /// Resolve all references used in a parameter. - /// - public override void Visit(AsyncApiParameter parameter) - { - this.ResolveObject(parameter.Schema, r => parameter.Schema = r); - } - - /// - /// Resolve all references used in a schema. - /// - public override void Visit(AsyncApiJsonSchema 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 entity, Action assign) - where T : class, IAsyncApiReferenceable, new() - { - if (entity == null) - { - return; - } - - if (this.IsUnresolvedReference(entity)) - { - assign(this.ResolveReference(entity.Reference)); - } - } - - private void ResolveList(IList 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(entity.Reference); - } - } - } - - private void ResolveMap(IDictionary 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(entity.Reference); - } - } - } - - private T ResolveReference(AsyncApiReference reference) - where T : class, IAsyncApiReferenceable, new() - { - // external references are resolved by the AsyncApiExternalReferenceResolver - if (reference.IsExternal) - { - return new() - { - UnresolvedReference = true, - Reference = reference, - }; - } - - try - { - var resolvedReference = this.currentDocument.ResolveReference(reference) as T; - if (resolvedReference == null) - { - throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'."); - } - - return resolvedReference; - } - catch (AsyncApiException ex) - { - this.errors.Add(new AsyncApiReferenceError(ex)); - return null; - } - } - - private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) - { - return (possibleReference != null && possibleReference.UnresolvedReference); - } + //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); + //} + + ///// + ///// Resolve all references used in an operation. + ///// + //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); + + // // #ToFix Resolve references correctly + // if (message.Payload is AsyncApiJsonSchemaPayload) + // { + // this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); + //} + + ///// + ///// Resolve all references to SecuritySchemes. + ///// + //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); + // } + // }); + // } + //} + + ///// + ///// Resolve all references to parameters. + ///// + //public override void Visit(IList parameters) + //{ + // this.ResolveList(parameters); + //} + + ///// + ///// Resolve all references used in a parameter. + ///// + //public override void Visit(AsyncApiParameter parameter) + //{ + // this.ResolveObject(parameter.Schema, r => parameter.Schema = r); + //} + + ///// + ///// Resolve all references used in a schema. + ///// + //public override void Visit(AsyncApiJsonSchema 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 entity, Action assign) + // where T : class, IAsyncApiReferenceable, new() + //{ + // if (entity == null) + // { + // return; + // } + + // if (this.IsUnresolvedReference(entity)) + // { + // assign(this.ResolveReference(entity.Reference)); + // } + //} + + //private void ResolveList(IList 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(entity.Reference); + // } + // } + //} + + //private void ResolveMap(IDictionary 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(entity.Reference); + // } + // } + //} + + //private T ResolveReference(AsyncApiReference reference) + // where T : class, IAsyncApiReferenceable, new() + //{ + // // external references are resolved by the AsyncApiExternalReferenceResolver + // if (reference.IsExternal) + // { + // return new() + // { + // UnresolvedReference = true, + // Reference = reference, + // }; + // } + + // try + // { + // var resolvedReference = this.currentDocument.ResolveReference(reference); + // if (resolvedReference == null) + // { + // throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'."); + // } + + // return resolvedReference; + // } + // catch (AsyncApiException ex) + // { + // this.errors.Add(new AsyncApiReferenceError(ex)); + // return null; + // } + //} + + //private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) + //{ + // return (possibleReference != null && possibleReference.UnresolvedReference); + //} } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 6007181e..0c2b42d6 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -718,8 +718,9 @@ internal void Walk(AsyncApiInfo info) internal void Walk(AsyncApiServer server, bool isComponent = false) { - if (server == null || this.ProcessAsReference(server, isComponent)) + if (server is AsyncApiServerReference) { + this.Walk(server as IAsyncApiReferenceable); return; } diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index a8b2accd..c2ea1e3a 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -174,7 +174,7 @@ private void Validate(object item, Type type) // Validate unresolved references as references var potentialReference = item as IAsyncApiReferenceable; - if (potentialReference != null && potentialReference.UnresolvedReference) + if (potentialReference != null) { type = typeof(IAsyncApiReferenceable); } diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs index f7b32dd9..8d32d57e 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs @@ -28,16 +28,6 @@ static AsyncApiYamlWriter() YamlControlCharacters = new char[] { '\0', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', }; } - /// - /// Initializes a new instance of the class. - /// - /// The text writer. - [Obsolete($"Please use overridden constructor that takes in a {nameof(AsyncApiWriterSettings)} instance.")] - public AsyncApiYamlWriter(TextWriter textWriter) - : this(textWriter, new AsyncApiWriterSettings()) - { - } - /// /// Initializes a new instance of the class. /// diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index a6aac3c8..b9391b2f 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1120,7 +1120,7 @@ public void SerializeV2_WithFullSpec_Serializes() }; var outputString = new StringWriter(); - var writer = new AsyncApiYamlWriter(outputString); + var writer = new AsyncApiYamlWriter(outputString, AsyncApiWriterSettings.Default); // Act document.SerializeV2(writer); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index cb97179d..b32ad744 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -234,7 +234,7 @@ public void AsyncApiMessage_WithAvroAsReference_Deserializes() var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); // Assert - deserializedMessage.Payload.UnresolvedReference.Should().BeTrue(); + //deserializedMessage.Payload.UnresolvedReference.Should().BeTrue(); deserializedMessage.Payload.Reference.Should().NotBeNull(); deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index d8e28e49..f87a89a1 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -319,35 +319,35 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect payload.Properties.Count.Should().Be(3); } - [Test] - public void AvroReference_WithExternalResourcesInterface_DeserializesCorrectly() - { - var yaml = """ - asyncapi: 2.3.0 - info: - title: test - version: 1.0.0 - channels: - workspace: - publish: - message: - schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0' - payload: - $ref: 'path/to/user-create.avsc/#UserCreate' - """; - var settings = new AsyncApiReaderSettings - { - ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceReader = new MockExternalAvroReferenceReader(), - }; - var reader = new AsyncApiStringReader(settings); - var doc = reader.Read(yaml, out var diagnostic); - var payload = doc.Channels["workspace"].Publish.Message.First().Payload; - payload.Should().BeAssignableTo(typeof(AsyncApiAvroSchemaPayload)); - var avro = payload as AsyncApiAvroSchemaPayload; - avro.TryGetAs(out var record); - record.Name.Should().Be("SomeEvent"); - } + //[Test] + //public void AvroReference_WithExternalResourcesInterface_DeserializesCorrectly() + //{ + // var yaml = """ + // asyncapi: 2.3.0 + // info: + // title: test + // version: 1.0.0 + // channels: + // workspace: + // publish: + // message: + // schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0' + // payload: + // $ref: 'path/to/user-create.avsc/#UserCreate' + // """; + // var settings = new AsyncApiReaderSettings + // { + // ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + // ExternalReferenceReader = new MockExternalAvroReferenceReader(), + // }; + // var reader = new AsyncApiStringReader(settings); + // var doc = reader.Read(yaml, out var diagnostic); + // var payload = doc.Channels["workspace"].Publish.Message.First().Payload; + // payload.Should().BeAssignableTo(typeof(AsyncApiAvroSchemaPayload)); + // var avro = payload as AsyncApiAvroSchemaPayload; + // avro.TryGetAs(out var record); + // record.Name.Should().Be("SomeEvent"); + //} } public class MockLoader : IStreamLoader diff --git a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs new file mode 100644 index 00000000..43090bc9 --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs @@ -0,0 +1,72 @@ +// Copyright (c) The LEGO Group. All rights reserved. +namespace LEGO.AsyncAPI.Tests +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using FluentAssertions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Readers; + using LEGO.AsyncAPI.Readers.V2; + using NUnit.Framework; + + public class ReferenceTests + { + [Test] + public void ReferencePointers() + { + var diag = new AsyncApiDiagnostic(); + var versionService = new AsyncApiV2VersionService(diag); + var externalFragment = versionService.ConvertToAsyncApiReference("https://github.com/test/test#whatever", ReferenceType.None); + var internalFragment = versionService.ConvertToAsyncApiReference("#/components/servers/server1", ReferenceType.None); + var localFile = versionService.ConvertToAsyncApiReference("./local/some/folder/whatever.yaml", ReferenceType.None); + var externalFile = versionService.ConvertToAsyncApiReference("https://github.com/test/whatever.yaml", ReferenceType.None); + + externalFragment.ExternalResource.Should().Be("https://github.com/test/test"); + externalFragment.Id.Should().Be("whatever"); + externalFragment.IsFragment.Should().BeTrue(); + externalFragment.IsExternal.Should().BeTrue(); + + internalFragment.ExternalResource.Should().Be(null); + internalFragment.Id.Should().Be("#/components/servers/server1"); + internalFragment.IsFragment.Should().BeTrue(); + internalFragment.IsExternal.Should().BeFalse(); + + localFile.ExternalResource.Should().Be("./local/some/folder/whatever.yaml"); + localFile.Id.Should().Be(null); + localFile.IsFragment.Should().BeFalse(); + + externalFile.ExternalResource.Should().Be("https://github.com/test/whatever.yaml"); + externalFile.Id.Should().Be(null); + externalFile.IsFragment.Should().BeFalse(); + } + + [Test] + public void Reference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "https://github.com/test/test#whatever" + } + } + } + """; + + var doc = new AsyncApiStringReader().Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + reference.Reference.ExternalResource.Should().Be("https://github.com/test/test"); + reference.Reference.Id.Should().Be("whatever"); + reference.Reference.HostDocument.Should().Be(doc); + reference.Reference.IsFragment.Should().BeTrue(); + + } + } +} \ No newline at end of file From 7200432f61ec1cf225a2bd92420dd218a82de770 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 24 Jan 2025 11:15:19 +0100 Subject: [PATCH 04/20] fragment work --- .../LEGO.AsyncAPI.Bindings.csproj | 1 + .../AsyncApiJsonDocumentReader.cs | 186 +++++++------ .../AsyncApiReferenceHostDocumentResolver.cs | 257 ++++++++++++++++++ .../LEGO.AsyncAPI.Readers.csproj | 2 + .../ParseNodes/ValueNode.cs | 1 + .../AsyncApiRemoteReferenceCollector.cs | 30 +- .../Services/DefaultStreamLoader.cs | 60 ++-- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 59 +--- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 3 +- .../Models/AsyncApiComponents.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 15 +- ...ayload.cs => AsyncApiJsonSchemaPayload.cs} | 0 src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 10 +- .../Interfaces/IAsyncApiReferenceable.cs | 5 + ...syncApiSchema.cs => AsyncApiJsonSchema.cs} | 0 .../References/AsyncApiServerReference.cs | 1 + .../Services/AsyncApiReferenceResolver.cs | 235 +--------------- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 2 +- .../Writers/AsyncApiWriterSettings.cs | 10 +- .../AsyncApiLicenseTests.cs | 2 +- .../Bindings/BindingExtensions_Should.cs | 6 +- .../LEGO.AsyncAPI.Tests.csproj | 1 - .../Models/AsyncApiSchema_Should.cs | 4 +- test/LEGO.AsyncAPI.Tests/ReferenceTests.cs | 69 +++++ 24 files changed, 525 insertions(+), 436 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs rename src/LEGO.AsyncAPI/Models/{AsyncApiSchemaPayload.cs => AsyncApiJsonSchemaPayload.cs} (100%) rename src/LEGO.AsyncAPI/Models/JsonSchema/{AsyncApiSchema.cs => AsyncApiJsonSchema.cs} (100%) diff --git a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj index 9c0b025e..8bce8ef8 100644 --- a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj +++ b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj @@ -5,6 +5,7 @@ AsyncAPI.NET.Bindings LEGO.AsyncAPI.Bindings LEGO.AsyncAPI.Bindings + netstandard2.0;net8 diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index e66ed58c..d38992f3 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -4,14 +4,18 @@ namespace LEGO.AsyncAPI.Readers { using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; + using Json.Pointer; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.Interface; using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Services; @@ -23,6 +27,7 @@ namespace LEGO.AsyncAPI.Readers internal class AsyncApiJsonDocumentReader : IAsyncApiReader { private readonly AsyncApiReaderSettings settings; + private ParsingContext context; /// /// Initializes a new instance of the class. @@ -42,7 +47,7 @@ public AsyncApiJsonDocumentReader(AsyncApiReaderSettings settings = null) public AsyncApiDocument Read(JsonNode input, out AsyncApiDiagnostic diagnostic) { diagnostic = new AsyncApiDiagnostic(); - var context = new ParsingContext(diagnostic, this.settings) + this.context ??= new ParsingContext(diagnostic, this.settings) { ExtensionParsers = this.settings.ExtensionParsers, ServerBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), @@ -54,8 +59,7 @@ public AsyncApiDocument Read(JsonNode input, out AsyncApiDiagnostic diagnostic) AsyncApiDocument document = null; try { - document = context.Parse(input); - + document = this.context.Parse(input); this.ResolveReferences(diagnostic, document); } catch (AsyncApiException ex) @@ -83,16 +87,20 @@ public AsyncApiDocument Read(JsonNode input, out AsyncApiDiagnostic diagnostic) public async Task ReadAsync(JsonNode input, CancellationToken cancellationToken = default) { var diagnostic = new AsyncApiDiagnostic(); - var context = new ParsingContext(diagnostic, this.settings) + this.context ??= new ParsingContext(diagnostic, this.settings) { ExtensionParsers = this.settings.ExtensionParsers, + ServerBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), + ChannelBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), + OperationBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), + MessageBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), }; AsyncApiDocument document = null; try { // Parse the AsyncApi Document - document = context.Parse(input); + document = this.context.Parse(input); this.ResolveReferences(diagnostic, document); } catch (AsyncApiException ex) @@ -133,7 +141,7 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi where T : IAsyncApiElement { diagnostic = new AsyncApiDiagnostic(); - var context = new ParsingContext(diagnostic, this.settings) + this.context ??= new ParsingContext(diagnostic, this.settings) { ExtensionParsers = this.settings.ExtensionParsers, ServerBindingParsers = this.settings.Bindings.OfType>().ToDictionary(b => b.BindingKey, b => b), @@ -146,7 +154,7 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi try { // Parse the AsyncApi element - element = context.ParseFragment(input, version); + element = this.context.ParseFragment(input, version); } catch (AsyncApiException ex) { @@ -184,106 +192,122 @@ private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument d private void ResolveAllReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) { this.ResolveInternalReferences(diagnostic, document); - this.ResolveExternalReferences(diagnostic, document); + this.ResolveExternalReferences(diagnostic, document, document); } private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) { - var errors = new List(); - var reader = new AsyncApiStringReader(this.settings); - errors.AddRange(document.ResolveReferences()); - foreach (var item in errors) + var resolver = new AsyncApiReferenceHostDocumentResolver(document); + var walker = new AsyncApiWalker(resolver); + walker.Walk(document); + + foreach (var item in resolver.Errors) { diagnostic.Errors.Add(item); } } - private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable, AsyncApiDocument hostDocument) { - var loader = this.settings.ExternalReferenceLoader ?? new DefaultStreamLoader(this.settings.BaseUrl); - var collector = new AsyncApiRemoteReferenceCollector(); + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); + var collector = new AsyncApiRemoteReferenceCollector(hostDocument); var walker = new AsyncApiWalker(collector); - walker.Walk(document); + walker.Walk(serializable); - var reader = new AsyncApiStreamReader(this.settings); foreach (var reference in collector.References) { - var input = loader.Load(new Uri(reference.ExternalResource, UriKind.RelativeOrAbsolute)); + if (this.context.Workspace.Contains(reference.Reference.Reference)) + { + continue; + } - // If Id is not null, the reference is for a fragment of a full document. - if (reference.Id != null) + try { - var result = reader.Read(input, out var streamDiagnostics); // How about avro?! - if (streamDiagnostics.Warnings.Any() || streamDiagnostics.Errors.Any()) + var input = loader.Load(new Uri(reference.Reference.Reference, UriKind.RelativeOrAbsolute)); + var component = this.ReadStreamFragment(input, reference, diagnostic); + if (component == null) { - diagnostic.Append(streamDiagnostics, reference.ExternalResource); + diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); + continue; } - if (result != null) - { - this.ResolveAllReferences(diagnostic, result); - } + this.context.Workspace.RegisterComponent(reference.Reference.Reference, component); + this.ResolveExternalReferences(diagnostic, component, hostDocument); } - else + catch (AsyncApiException ex) { - // If id IS null, its a fragment that we can resolve directly. - // #TODO Use proxy references for easier fragment resolution. - IAsyncApiElement result = null; - switch (reference.Type) - { - case ReferenceType.Schema: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); - break; - //case ReferenceType.Server: - // result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.Channel: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.Message: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.SecurityScheme: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.Parameter: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.CorrelationId: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.OperationTrait: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.MessageTrait: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.ServerBindings: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.ChannelBindings: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.OperationBindings: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - case ReferenceType.MessageBindings: - result = reader.ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); - break; - default: - diagnostic.Errors.Add(new AsyncApiError(reference.Reference, "Could not resolve reference.")); - break; - } + diagnostic.Errors.Add(new AsyncApiError(ex)); + } + } + } - if (result != null) - { - this.ResolveAllReferences(diagnostic, document); - } + private IAsyncApiSerializable ReadStreamFragment(Stream input, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + { + var json = JsonNode.Parse(input); + if (reference.Reference.IsFragment) + { + var pointer = JsonPointer.Parse(reference.Reference.Id); + if (pointer.TryEvaluate(json, out var pointerResult)) + { + json = pointerResult; + } + else + { + diagnostic.Errors.Add(new AsyncApiError(reference.Reference.Reference, "Could not resolve reference fragment.")); + return null; + } } + IAsyncApiSerializable result = null; + switch (reference.Reference.Type) + { + case ReferenceType.Schema: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); + break; + case ReferenceType.Server: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Channel: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Message: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.SecurityScheme: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.Parameter: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.CorrelationId: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.OperationTrait: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.MessageTrait: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.ServerBindings: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.ChannelBindings: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.OperationBindings: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + case ReferenceType.MessageBindings: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + break; + default: + diagnostic.Errors.Add(new AsyncApiError(reference.Reference.Reference, "Could not resolve reference.")); + break; } + + return result; } } -} \ No newline at end of file +} diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs new file mode 100644 index 00000000..5f99ece0 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs @@ -0,0 +1,257 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json.Nodes; + using System.Threading; + using System.Threading.Tasks; + using LEGO.AsyncAPI.Exceptions; + using LEGO.AsyncAPI.Extensions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Readers.Interface; + using LEGO.AsyncAPI.Readers.Services; + using LEGO.AsyncAPI.Services; + using LEGO.AsyncAPI.Validations; + + + internal class AsyncApiReferenceHostDocumentResolver : AsyncApiVisitorBase + { + private AsyncApiDocument currentDocument; + private List errors = new List(); + + public AsyncApiReferenceHostDocumentResolver( + AsyncApiDocument currentDocument) + { + this.currentDocument = currentDocument; + } + + public IEnumerable Errors + { + get + { + return this.errors; + } + } + + public override void Visit(IAsyncApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.HostDocument = this.currentDocument; + this.currentDocument.Workspace.RegisterReference(referenceable); + } + + } + } + //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); + //} + + ///// + ///// Resolve all references used in an operation. + ///// + //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); + + // // #ToFix Resolve references correctly + // if (message.Payload is AsyncApiJsonSchemaPayload) + // { + // this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); + //} + + ///// + ///// Resolve all references to SecuritySchemes. + ///// + //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); + // } + // }); + // } + //} + + ///// + ///// Resolve all references to parameters. + ///// + //public override void Visit(IList parameters) + //{ + // this.ResolveList(parameters); + //} + + ///// + ///// Resolve all references used in a parameter. + ///// + //public override void Visit(AsyncApiParameter parameter) + //{ + // this.ResolveObject(parameter.Schema, r => parameter.Schema = r); + //} + + ///// + ///// Resolve all references used in a schema. + ///// + //public override void Visit(AsyncApiJsonSchema 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 entity, Action assign) + // where T : class, IAsyncApiReferenceable, new() + //{ + // if (entity == null) + // { + // return; + // } + + // if (this.IsUnresolvedReference(entity)) + // { + // assign(this.ResolveReference(entity.Reference)); + // } + //} + + //private void ResolveList(IList 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(entity.Reference); + // } + // } + //} + + //private void ResolveMap(IDictionary 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(entity.Reference); + // } + // } + //} + + //private T ResolveReference(AsyncApiReference reference) + // where T : class, IAsyncApiReferenceable, new() + //{ + // // external references are resolved by the AsyncApiExternalReferenceResolver + // if (reference.IsExternal) + // { + // return new() + // { + // UnresolvedReference = true, + // Reference = reference, + // }; + // } + + // try + // { + // var resolvedReference = this.currentDocument.ResolveReference(reference); + // if (resolvedReference == null) + // { + // throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'."); + // } + + // return resolvedReference; + // } + // catch (AsyncApiException ex) + // { + // this.errors.Add(new AsyncApiReferenceError(ex)); + // return null; + // } + //} + + //private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) + //{ + // return (possibleReference != null && possibleReference.UnresolvedReference); + //} + //} +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj index 53071ca0..1fb2c474 100644 --- a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj +++ b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj @@ -6,6 +6,7 @@ AsyncAPI.NET.Readers LEGO.AsyncAPI.Readers LEGO.AsyncAPI.Readers + netstandard2.0;net8 @@ -17,6 +18,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs index 76add0b2..246018b4 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs @@ -12,6 +12,7 @@ public class ValueNode : ParseNode { private readonly JsonNode node; private string cachedScalarValue; + public ValueNode(ParsingContext context, JsonNode node) : base( context) diff --git a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs index 37fde872..48c710e1 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -9,16 +9,22 @@ namespace LEGO.AsyncAPI.Readers.Services internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase { - private readonly Dictionary references = new(); + private readonly List references = new(); + private AsyncApiDocument currentDocument; + public AsyncApiRemoteReferenceCollector( + AsyncApiDocument currentDocument) + { + this.currentDocument = currentDocument; + } /// /// List of all external references collected from AsyncApiDocument. /// - public IEnumerable References + public IEnumerable References { get { - return this.references.Values; + return this.references; } } @@ -28,18 +34,14 @@ public IEnumerable References /// public override void Visit(IAsyncApiReferenceable referenceable) { - this.AddExternalReferences(referenceable.Reference); - } - - /// - /// Collect external references. - /// - private void AddExternalReferences(AsyncApiReference reference) - { - if (reference is { IsExternal: true } && - !this.references.ContainsKey(reference.ExternalResource)) + if (referenceable.Reference != null && referenceable.Reference.IsExternal) { - this.references.Add(reference.ExternalResource, reference); + if (referenceable.Reference.HostDocument == null) + { + referenceable.Reference.HostDocument = this.currentDocument; + } + + this.references.Add(referenceable); } } } diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs index 18f41873..98b710c7 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs @@ -6,46 +6,54 @@ namespace LEGO.AsyncAPI.Readers.Services using System.IO; using System.Net.Http; using System.Threading.Tasks; + using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.Interface; internal class DefaultStreamLoader : IStreamLoader { - private readonly Uri baseUrl; - private HttpClient httpClient = new HttpClient(); - - public DefaultStreamLoader(Uri baseUrl) - { - this.baseUrl = baseUrl; - } + private static readonly HttpClient httpClient = new HttpClient(); public Stream Load(Uri uri) { - - uri = new Uri(this.baseUrl, uri); - switch (uri.Scheme) + try + { + switch (uri.Scheme) + { + case "file": + return File.OpenRead(uri.AbsolutePath); + case "http": + case "https": + return httpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); + default: + throw new ArgumentException("Unsupported scheme"); + } + } + catch (Exception ex) { - case "file": - return File.OpenRead(uri.AbsolutePath); - case "http": - case "https": - return this.httpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); - default: - throw new ArgumentException("Unsupported scheme"); + + throw new AsyncApiReaderException($"Something went wrong trying to fetch '{uri.OriginalString}. {ex.Message}'", ex); } } public async Task LoadAsync(Uri uri) { - uri = new Uri(this.baseUrl, uri); - switch (uri.Scheme) + try { - case "file": - return File.OpenRead(uri.AbsolutePath); - case "http": - case "https": - return await this.httpClient.GetStreamAsync(uri); - default: - throw new ArgumentException("Unsupported scheme"); + switch (uri.Scheme) + { + case "file": + return File.OpenRead(uri.AbsolutePath); + case "http": + case "https": + return await httpClient.GetStreamAsync(uri); + default: + throw new ArgumentException("Unsupported scheme"); + } + } + catch (Exception ex) + { + + throw new AsyncApiReaderException($"Something went wrong trying to fetch '{uri.OriginalString}'. {ex.Message}", ex); } } } diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index b52985ca..921686b7 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -10,21 +10,9 @@ namespace LEGO.AsyncAPI public class AsyncApiWorkspace { - private readonly Dictionary documentsIdRegistry = new(); + private readonly Dictionary referenceRegistry = new(); private readonly Dictionary artifactsRegistry = new(); - private readonly Dictionary asyncApiReferenceableRegistry = new(); - - public Uri BaseUrl { get; } - - public AsyncApiWorkspace() - { - this.BaseUrl = new Uri("http://asyncapi.net"); - } - - public AsyncApiWorkspace(Uri baseUrl) - { - this.BaseUrl = baseUrl; - } + private readonly Dictionary resolvedReferenceRegistry = new(); public void RegisterComponents(AsyncApiDocument document) { @@ -143,31 +131,14 @@ public void RegisterComponents(AsyncApiDocument document) } } - private void RegisterInternalComponent(string location, IAsyncApiReferenceable component) - { - var uri = this.ToLocationUrl(location); - if (!this.asyncApiReferenceableRegistry.ContainsKey(uri)) - { - this.asyncApiReferenceableRegistry[uri] = component; - } - } - public bool RegisterComponent(string location, T component) { var uri = this.ToLocationUrl(location); if (component is IAsyncApiSerializable referenceable) { - if (!this.asyncApiReferenceableRegistry.ContainsKey(uri)) - { - this.asyncApiReferenceableRegistry[uri] = referenceable; - return true; - } - } - else if (component is Stream stream) - { - if (!this.artifactsRegistry.ContainsKey(uri)) + if (!this.resolvedReferenceRegistry.ContainsKey(uri)) { - this.artifactsRegistry[uri] = stream; + this.resolvedReferenceRegistry[uri] = referenceable; return true; } } @@ -175,22 +146,16 @@ public bool RegisterComponent(string location, T component) return false; } - public void AddDocumentId(string key, Uri value) + public void RegisterReference(IAsyncApiReferenceable reference) { - if (!this.documentsIdRegistry.ContainsKey(key)) - { - this.documentsIdRegistry[key] = value; - } + var location = reference.Reference.Reference; + this.referenceRegistry[location] = reference; } - public Uri GetDocumentId(string key) + public bool Contains(string location) { - if (this.documentsIdRegistry.TryGetValue(key, out var id)) - { - return id; - } - - return null; + var key = this.ToLocationUrl(location); + return this.resolvedReferenceRegistry.ContainsKey(key); } public T ResolveReference(string location) @@ -201,7 +166,7 @@ public T ResolveReference(string location) } var uri = this.ToLocationUrl(location); - if (this.asyncApiReferenceableRegistry.TryGetValue(uri, out var referenceableValue)) + if (this.resolvedReferenceRegistry.TryGetValue(uri, out var referenceableValue)) { return (T)referenceableValue; } @@ -211,7 +176,7 @@ public T ResolveReference(string location) private Uri ToLocationUrl(string location) { - return new(this.BaseUrl, location); + return new(location, UriKind.RelativeOrAbsolute); } } } diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 99e69016..c73cc9d0 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -5,6 +5,7 @@ AsyncAPI.NET LEGO.AsyncAPI LEGO.AsyncAPI + netstandard2.0;net8 @@ -19,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + <_Parameter1>$(MSBuildProjectName).Tests diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index 54412d52..4b309c9c 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -97,7 +97,7 @@ public void SerializeV2(IAsyncApiWriter writer) // If references have been inlined we don't need the to render the components section // however if they have cycles, then we will need a component rendered - if (writer.GetSettings().InlineReferences) + if (writer.GetSettings().InlineLocalReferences) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index d3a1bb1c..e056f078 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -13,7 +13,7 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable { - internal AsyncApiWorkspace Workspace { get; set; } + public AsyncApiWorkspace Workspace { get; set; } /// /// REQUIRED. Specifies the AsyncAPI Specification version being used. @@ -74,11 +74,6 @@ public void SerializeV2(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - if (writer.GetSettings().InlineReferences) - { - this.ResolveReferences(); - } - writer.WriteStartObject(); // asyncApi @@ -114,14 +109,6 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteEndObject(); } - public IEnumerable ResolveReferences() - { - var resolver = new AsyncApiReferenceResolver(this); - var walker = new AsyncApiWalker(resolver); - walker.Walk(this); - return resolver.Errors; - } - internal T? ResolveReference(AsyncApiReference reference) where T : class { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs similarity index 100% rename from src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs rename to src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index d7151e0f..8093bc62 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -99,10 +99,10 @@ public AsyncApiReference(string reference, ReferenceType? type) /// 1. a absolute/relative file path, for example: ../commons/pet.json /// 2. a Url, for example: http://localhost/pet.json. /// - public string ExternalResource - { - get; - set; + public string ExternalResource + { + get; + set; } /// @@ -184,7 +184,7 @@ public void SerializeV2(IAsyncApiWriter writer) private string GetExternalReferenceV2() { - return this.ExternalResource + "/#/" + this.Id ?? string.Empty; + return this.ExternalResource + (this.Id != null ? "/#/" + this.Id : string.Empty); } public void Write(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs index 0db58a90..b5480e51 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs @@ -6,6 +6,11 @@ namespace LEGO.AsyncAPI.Models.Interfaces public interface IAsyncApiReferenceable : IAsyncApiSerializable { + /// + /// Indicates if object is populated with data or is just a reference to the data. + /// + bool UnresolvedReference { get; set; } + /// /// Reference object. /// diff --git a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs similarity index 100% rename from src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs rename to src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs index 2392e1ef..ec1bcc4a 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -47,6 +47,7 @@ public AsyncApiServerReference(string reference) public override IDictionary Extensions { get => this.Target.Extensions; set => this.Target.Extensions = value; } public AsyncApiReference Reference { get; set; } + public bool UnresolvedReference { get; set; } public override void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 17228226..728eb5db 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -11,238 +11,5 @@ namespace LEGO.AsyncAPI.Services /// /// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects. /// - internal class AsyncApiReferenceResolver : AsyncApiVisitorBase - { - private AsyncApiDocument currentDocument; - private List errors = new List(); - - public AsyncApiReferenceResolver( - AsyncApiDocument currentDocument) - { - this.currentDocument = currentDocument; - } - - public IEnumerable 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); - //} - - ///// - ///// Resolve all references used in an operation. - ///// - //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); - - // // #ToFix Resolve references correctly - // if (message.Payload is AsyncApiJsonSchemaPayload) - // { - // this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); - //} - - ///// - ///// Resolve all references to SecuritySchemes. - ///// - //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); - // } - // }); - // } - //} - - ///// - ///// Resolve all references to parameters. - ///// - //public override void Visit(IList parameters) - //{ - // this.ResolveList(parameters); - //} - - ///// - ///// Resolve all references used in a parameter. - ///// - //public override void Visit(AsyncApiParameter parameter) - //{ - // this.ResolveObject(parameter.Schema, r => parameter.Schema = r); - //} - - ///// - ///// Resolve all references used in a schema. - ///// - //public override void Visit(AsyncApiJsonSchema 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 entity, Action assign) - // where T : class, IAsyncApiReferenceable, new() - //{ - // if (entity == null) - // { - // return; - // } - - // if (this.IsUnresolvedReference(entity)) - // { - // assign(this.ResolveReference(entity.Reference)); - // } - //} - - //private void ResolveList(IList 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(entity.Reference); - // } - // } - //} - - //private void ResolveMap(IDictionary 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(entity.Reference); - // } - // } - //} - - //private T ResolveReference(AsyncApiReference reference) - // where T : class, IAsyncApiReferenceable, new() - //{ - // // external references are resolved by the AsyncApiExternalReferenceResolver - // if (reference.IsExternal) - // { - // return new() - // { - // UnresolvedReference = true, - // Reference = reference, - // }; - // } - - // try - // { - // var resolvedReference = this.currentDocument.ResolveReference(reference); - // if (resolvedReference == null) - // { - // throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'."); - // } - - // return resolvedReference; - // } - // catch (AsyncApiException ex) - // { - // this.errors.Add(new AsyncApiReferenceError(ex)); - // return null; - // } - //} - - //private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) - //{ - // return (possibleReference != null && possibleReference.UnresolvedReference); - //} - } + } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 3ef78269..10a140db 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -331,7 +331,7 @@ internal void Walk(AsyncApiAvroSchemaPayload payload) this.visitor.Visit(payload); } - internal void Walk(AsyncApiSchema schema, bool isComponent = false) + internal void Walk(AsyncApiJsonSchema schema, bool isComponent = false) { if (schema == null || this.ProcessAsReference(schema, isComponent)) { diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs index 2b663bb2..1d866308 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs @@ -21,7 +21,7 @@ static AsyncApiWriterSettings() /// public AsyncApiWriterSettings() { - this.InlineReferences = false; + this.InlineLocalReferences = false; this.LoopDetector = new LoopDetector(); } @@ -46,10 +46,10 @@ public ReferenceInlineSetting ReferenceInline switch (this.referenceInline) { case ReferenceInlineSetting.DoNotInlineReferences: - this.InlineReferences = false; + this.InlineLocalReferences = false; break; case ReferenceInlineSetting.InlineReferences: - this.InlineReferences = true; + this.InlineLocalReferences = true; break; } } @@ -58,7 +58,7 @@ public ReferenceInlineSetting ReferenceInline /// /// Gets or sets a value indicating whether indicates if local references should be rendered as an inline object. /// - public bool InlineReferences { get; set; } + public bool InlineLocalReferences { get; set; } /// /// Figures out if a loop exists. @@ -72,7 +72,7 @@ public ReferenceInlineSetting ReferenceInline /// True if it should be inlined otherwise false. public bool ShouldInlineReference(AsyncApiReference reference) { - return this.InlineReferences; + return this.InlineLocalReferences; } } } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiLicenseTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiLicenseTests.cs index 1edcbe37..97fd1634 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiLicenseTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiLicenseTests.cs @@ -70,7 +70,7 @@ public void LoadLicense_WithJson_Deserializes() var settings = new AsyncApiReaderSettings(); var context = new ParsingContext(diagnostic, settings); - var node = new MapNode(context, JsonNode.Parse(stream)); + var node = new MapNode(context, JsonNode.Parse(stream).ToJsonString()); // Act var actual = AsyncApiV2Deserializer.LoadLicense(node); diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs index 3806fc03..a0c8cb88 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs @@ -19,11 +19,11 @@ public void TryGetValue_WithChannelBinding_ReturnsBinding() channel.Bindings.Add(new WebSocketsChannelBinding { Method = "POST", - Query = new AsyncApiSchema + Query = new AsyncApiJsonSchema { Description = "this mah query", }, - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Description = "this mah binding", }, @@ -74,7 +74,7 @@ public void TryGetValue_WithMessageBinding_ReturnsBinding() message.Bindings.Add(new MQTTMessageBinding { PayloadFormatIndicator = 2, - CorrelationData = new AsyncApiSchema + CorrelationData = new AsyncApiJsonSchema { Description = "Test", }, diff --git a/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj index bd35d2ae..c70af517 100644 --- a/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj +++ b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj @@ -13,7 +13,6 @@ - diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 7e00efab..30ce0564 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -370,7 +370,7 @@ public void SerializeAsJson_WithAdvancedSchemaObject_V2Works() public void Deserialize_WithAdvancedSchema_Works() { // Arrange - var json = GetTestData(); + var json = this.GetTestData(); var expected = AdvancedSchemaObject; // Act @@ -447,7 +447,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl .Build(); var outputString = new StringWriter(); - var writer = new AsyncApiYamlWriter(outputString, new AsyncApiWriterSettings { InlineReferences = shouldInline }); + var writer = new AsyncApiYamlWriter(outputString, new AsyncApiWriterSettings { InlineLocalReferences = shouldInline }); // Act asyncApiDocument.SerializeV2(writer); diff --git a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs index 43090bc9..81dc2018 100644 --- a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs +++ b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs @@ -66,6 +66,75 @@ public void Reference() reference.Reference.Id.Should().Be("whatever"); reference.Reference.HostDocument.Should().Be(doc); reference.Reference.IsFragment.Should().BeTrue(); + } + + [Test] + public void ResolveExternalReference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "https://gist.githubusercontent.com/VisualBean/7dc9607d735122483e1bb7005ff3ad0e/raw/458729e4d56636ef3bb34762f4a5731ea5043bdf/servers.json/#/servers/0" + } + } + } + """; + + var doc = new AsyncApiStringReader(new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences }).Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + //reference.Reference.Id.Should().Be("whatever"); + //reference.Reference.HostDocument.Should().Be(doc); + //reference.Reference.IsFragment.Should().BeTrue(); + } + + + [Test] + public void ServerReference_WithComponentReference_ResolvesReference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "#/components/servers/whatever" + } + }, + "components": { + "servers": { + "whatever": { + "url": "wss://production.gigantic-server.com:443", + "protocol": "wss", + "protocolVersion": "1.0.0", + "description": "The production API server", + "variables": { + "username": { + "default": "demo", + "description": "This value is assigned by the service provider" + }, + "password": { + "default": "demo", + "description": "This value is assigned by the service provider" + } + } + } + } + } + } + """; + + var doc = new AsyncApiStringReader().Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + reference.Reference.ExternalResource.Should().BeNull(); + reference.Reference.Id.Should().Be("whatever"); + reference.Reference.HostDocument.Should().Be(doc); + reference.Reference.IsFragment.Should().BeTrue(); + reference.Url.Should().Be("wss://production.gigantic-server.com:443"); } } From b8b0b8d7b678e98b4dda1e8ad721a519c38c4d31 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 24 Jan 2025 15:40:15 +0100 Subject: [PATCH 05/20] more reference types --- .../AsyncApiJsonDocumentReader.cs | 15 ++- .../V2/AsyncApiChannelDeserializer.cs | 2 +- .../V2/AsyncApiComponentsDeserializer.cs | 10 +- .../V2/AsyncApiCorrelationIdDeserializer.cs | 2 +- .../V2/AsyncApiMessageDeserializer.cs | 2 +- .../V2/AsyncApiParameterDeserializer.cs | 2 +- .../V2/AsyncApiSecuritySchemeDeserializer.cs | 2 +- .../V2/AsyncApiServerVariableDeserializer.cs | 2 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 17 +++- src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs | 44 +++------ .../Models/AsyncApiComponents.cs | 36 +++---- .../Models/AsyncApiCorrelationId.cs | 32 +------ src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 58 ++++-------- src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs | 34 ++----- .../Models/AsyncApiSecurityRequirement.cs | 2 +- .../Models/AsyncApiSecurityScheme.cs | 93 +++---------------- .../Models/AsyncApiServerVariable.cs | 34 ++----- .../Interfaces/IAsyncApiReferenceable.cs | 2 +- .../References/AsyncApiChannelReference.cs | 57 ++++++++++++ .../AsyncApiCorrelationIdReference.cs | 52 +++++++++++ .../References/AsyncApiMessageReference.cs | 70 ++++++++++++++ .../References/AsyncApiParameterReference.cs | 54 +++++++++++ .../AsyncApiSecuritySchemeReference.cs | 64 +++++++++++++ .../References/AsyncApiServerReference.cs | 21 +++-- .../AsyncApiServerVariableReference.cs | 55 +++++++++++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 15 ++- .../AsyncApiDocumentV2Tests.cs | 36 +------ .../Models/AsyncApiChannel_Should.cs | 2 +- .../Models/AsyncApiReference_Should.cs | 4 +- 30 files changed, 492 insertions(+), 329 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index d38992f3..65993632 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -225,8 +225,17 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS try { - var input = loader.Load(new Uri(reference.Reference.Reference, UriKind.RelativeOrAbsolute)); - var component = this.ReadStreamFragment(input, reference, diagnostic); + Stream stream; + if (this.context.Workspace.Contains(reference.Reference.ExternalResource)) + { + stream = this.context.Workspace.ResolveReference(reference.Reference.ExternalResource); + } + else + { + stream = loader.Load(new Uri(reference.Reference.Reference, UriKind.RelativeOrAbsolute)); + } + + var component = this.ResolveStream(stream, reference, diagnostic); if (component == null) { diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); @@ -243,7 +252,7 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS } } - private IAsyncApiSerializable ReadStreamFragment(Stream input, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + private IAsyncApiSerializable ResolveStream(Stream input, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) { var json = JsonNode.Parse(input); if (reference.Reference.IsFragment) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs index 1d6acae6..7b9e3e63 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs @@ -30,7 +30,7 @@ public static AsyncApiChannel LoadChannel(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Channel, pointer); + return new AsyncApiChannelReference(pointer); } var pathItem = new AsyncApiChannel(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index fea37c45..53124676 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -12,11 +12,11 @@ internal static partial class AsyncApiV2Deserializer { { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, AsyncApiSchemaDeserializer.LoadSchema) }, { "servers", (a, n) => a.Servers = n.CreateMap(LoadServer) }, - { "channels", (a, n) => a.Channels = n.CreateMapWithReference(ReferenceType.Channel, LoadChannel) }, - { "messages", (a, n) => a.Messages = n.CreateMapWithReference(ReferenceType.Message, LoadMessage) }, - { "securitySchemes", (a, n) => a.SecuritySchemes = n.CreateMapWithReference(ReferenceType.SecurityScheme, LoadSecurityScheme) }, - { "parameters", (a, n) => a.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter) }, - { "correlationIds", (a, n) => a.CorrelationIds = n.CreateMapWithReference(ReferenceType.CorrelationId, LoadCorrelationId) }, + { "channels", (a, n) => a.Channels = n.CreateMap(LoadChannel) }, + { "messages", (a, n) => a.Messages = n.CreateMap(LoadMessage) }, + { "securitySchemes", (a, n) => a.SecuritySchemes = n.CreateMap(LoadSecurityScheme) }, + { "parameters", (a, n) => a.Parameters = n.CreateMap(LoadParameter) }, + { "correlationIds", (a, n) => a.CorrelationIds = n.CreateMap(LoadCorrelationId) }, { "operationTraits", (a, n) => a.OperationTraits = n.CreateMapWithReference(ReferenceType.OperationTrait, LoadOperationTrait) }, { "messageTraits", (a, n) => a.MessageTraits = n.CreateMapWithReference(ReferenceType.MessageTrait, LoadMessageTrait) }, { "serverBindings", (a, n) => a.ServerBindings = n.CreateMapWithReference(ReferenceType.ServerBindings, LoadServerBindings) }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiCorrelationIdDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiCorrelationIdDeserializer.cs index 0b8e88f0..ee908181 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiCorrelationIdDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiCorrelationIdDeserializer.cs @@ -31,7 +31,7 @@ public static AsyncApiCorrelationId LoadCorrelationId(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.CorrelationId, pointer); + return new AsyncApiCorrelationIdReference(pointer); } var correlationId = new AsyncApiCorrelationId(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 2670d88a..daf867bf 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -137,7 +137,7 @@ public static AsyncApiMessage LoadMessage(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Message, pointer); + return new AsyncApiMessageReference(pointer); } var message = new AsyncApiMessage(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs index c77f48f4..e7628c12 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs @@ -28,7 +28,7 @@ public static AsyncApiParameter LoadParameter(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer); + return new AsyncApiParameterReference(pointer); } var parameter = new AsyncApiParameter(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecuritySchemeDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecuritySchemeDeserializer.cs index 92708116..37481197 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecuritySchemeDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecuritySchemeDeserializer.cs @@ -56,7 +56,7 @@ public static AsyncApiSecurityScheme LoadSecurityScheme(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.SecurityScheme, pointer); + return new AsyncApiSecuritySchemeReference(pointer); } var securityScheme = new AsyncApiSecurityScheme(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerVariableDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerVariableDeserializer.cs index b773d255..a47ec6a6 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerVariableDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerVariableDeserializer.cs @@ -41,7 +41,7 @@ public static AsyncApiServerVariable LoadServerVariable(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.ServerVariable, pointer); + return new AsyncApiServerVariableReference(pointer); } var serverVariable = new AsyncApiServerVariable(); diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index 921686b7..dadb635a 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -5,6 +5,7 @@ namespace LEGO.AsyncAPI using System; using System.Collections.Generic; using System.IO; + using System.Text.Json.Nodes; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; @@ -143,6 +144,15 @@ public bool RegisterComponent(string location, T component) } } + if (component is Stream stream) + { + if (!this.artifactsRegistry.ContainsKey(uri)) + { + this.artifactsRegistry[uri] = stream; + } + return true; + } + return false; } @@ -155,7 +165,7 @@ public void RegisterReference(IAsyncApiReferenceable reference) public bool Contains(string location) { var key = this.ToLocationUrl(location); - return this.resolvedReferenceRegistry.ContainsKey(key); + return this.resolvedReferenceRegistry.ContainsKey(key) || this.artifactsRegistry.ContainsKey(key); } public T ResolveReference(string location) @@ -171,6 +181,11 @@ public T ResolveReference(string location) return (T)referenceableValue; } + if (this.artifactsRegistry.TryGetValue(uri, out var stream)) + { + return (T)(object)stream; + } + return default; } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs b/src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs index 2ff637f5..a7e25963 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs @@ -10,12 +10,12 @@ namespace LEGO.AsyncAPI.Models /// /// Describes the operations available on a single channel. /// - public class AsyncApiChannel : IAsyncApiReferenceable, IAsyncApiExtensible + public class AsyncApiChannel : IAsyncApiSerializable, IAsyncApiExtensible { /// /// an optional description of this channel item. CommonMark syntax can be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// the servers on which this channel is available, specified as an optional unordered list of names (string keys) of Server Objects defined in the Servers Object (a map). @@ -23,52 +23,32 @@ public class AsyncApiChannel : IAsyncApiReferenceable, IAsyncApiExtensible /// /// If servers is absent or empty then this channel must be available on all servers defined in the Servers Object. /// - public IList Servers { get; set; } = new List(); + public virtual IList Servers { get; set; } = new List(); /// /// a definition of the SUBSCRIBE operation, which defines the messages produced by the application and sent to the channel. /// - public AsyncApiOperation Subscribe { get; set; } + public virtual AsyncApiOperation Subscribe { get; set; } /// /// a definition of the PUBLISH operation, which defines the messages consumed by the application from the channel. /// - public AsyncApiOperation Publish { get; set; } + public virtual AsyncApiOperation Publish { get; set; } /// /// a map of the parameters included in the channel name. It SHOULD be present only when using channels with expressions (as defined by RFC 6570 section 2.2). /// - public IDictionary Parameters { get; set; } = new Dictionary(); + public virtual IDictionary Parameters { get; set; } = new Dictionary(); /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the channel. /// - public AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); + public virtual AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { @@ -92,11 +72,9 @@ public void SerializeV2WithoutReference(IAsyncApiWriter writer) // parameters writer.WriteOptionalMap(AsyncApiConstants.Parameters, this.Parameters, (writer, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Channel && - component.Reference.Id == key) + if (component is AsyncApiParameterReference reference) { - component.SerializeV2WithoutReference(writer); + reference.SerializeV2(writer); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index 4b309c9c..dece92ee 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -164,11 +164,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.ServerVariables, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.ServerVariable && - component.Reference.Id == key) + if (component is AsyncApiServerVariableReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -182,11 +180,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.Channels, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Channel && - component.Reference.Id == key) + if (component is AsyncApiChannelReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -200,11 +196,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.Messages, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Message && - component.Reference.Id == key) + if (component is AsyncApiMessageReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -218,11 +212,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.SecuritySchemes, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.SecurityScheme && - component.Reference.Id == key) + if (component is AsyncApiSecuritySchemeReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -236,11 +228,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.Parameters, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Parameter && - component.Reference.Id == key) + if (component is AsyncApiParameterReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -254,11 +244,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.CorrelationIds, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.CorrelationId && - component.Reference.Id == key) + if (component is AsyncApiCorrelationIdReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiCorrelationId.cs b/src/LEGO.AsyncAPI/Models/AsyncApiCorrelationId.cs index 156e7828..b3342110 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiCorrelationId.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiCorrelationId.cs @@ -10,44 +10,22 @@ namespace LEGO.AsyncAPI.Models /// /// An object that specifies an identifier at design time that can used for message tracing and correlation. /// - public class AsyncApiCorrelationId : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyncApiSerializable + public class AsyncApiCorrelationId : IAsyncApiExtensible, IAsyncApiSerializable { /// /// an optional description of the identifier. CommonMark syntax can be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// REQUIRED. A runtime expression that specifies the location of the correlation ID. /// - public string Location { get; set; } + public virtual string Location { get; set; } /// - public bool UnresolvedReference { get; set; } + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public AsyncApiReference Reference { get; set; } - - /// - public IDictionary Extensions { get; set; } = new Dictionary(); - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index e056f078..fe8e101c 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -110,7 +110,7 @@ public void SerializeV2(IAsyncApiWriter writer) } internal T? ResolveReference(AsyncApiReference reference) - where T : class + where T : IAsyncApiSerializable { return this.Workspace.ResolveReference(reference.Reference); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs index e0cc6776..b46629a2 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs @@ -10,27 +10,27 @@ namespace LEGO.AsyncAPI.Models /// /// Describes a message received on a given channel and operation. /// - public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiReferenceable, IAsyncApiSerializable + public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiSerializable { /// /// Unique string used to identify the message. The id MUST be unique among all messages described in the API. /// - public string MessageId { get; set; } + public virtual string MessageId { get; set; } /// /// schema definition of the application headers. Schema MUST be of type "object". /// - public AsyncApiJsonSchema Headers { get; set; } + public virtual AsyncApiJsonSchema Headers { get; set; } /// /// definition of the message payload. It can be of any type but defaults to Schema object. It must match the schema format, including encoding type - e.g Avro should be inlined as either a YAML or JSON object NOT a string to be parsed as YAML or JSON. /// - public IAsyncApiMessagePayload Payload { get; set; } + public virtual IAsyncApiMessagePayload Payload { get; set; } /// /// definition of the correlation ID used for message tracing or matching. /// - public AsyncApiCorrelationId CorrelationId { get; set; } + public virtual AsyncApiCorrelationId CorrelationId { get; set; } /// /// a string containing the name of the schema format used to define the message payload. @@ -38,84 +38,62 @@ public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiReferenceable, IAsy /// /// If omitted, implementations should parse the payload as a Schema object. /// - public string SchemaFormat { get; set; } + public virtual string SchemaFormat { get; set; } /// /// the content type to use when encoding/decoding a message's payload. /// - public string ContentType { get; set; } + public virtual string ContentType { get; set; } /// /// a machine-friendly name for the message. /// - public string Name { get; set; } + public virtual string Name { get; set; } /// /// a human-friendly title for the message. /// - public string Title { get; set; } + public virtual string Title { get; set; } /// /// a short summary of what the message is about. /// - public string Summary { get; set; } + public virtual string Summary { get; set; } /// /// a verbose explanation of the message. CommonMark syntax can be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// a list of tags for API documentation control. Tags can be used for logical grouping of messages. /// - public IList Tags { get; set; } = new List(); + public virtual IList Tags { get; set; } = new List(); /// /// additional external documentation for this message. /// - public AsyncApiExternalDocumentation ExternalDocs { get; set; } + public virtual AsyncApiExternalDocumentation ExternalDocs { get; set; } /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the message. /// - public AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); + public virtual AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); /// /// list of examples. /// - public IList Examples { get; set; } = new List(); + public virtual IList Examples { get; set; } = new List(); /// /// a list of traits to apply to the message object. Traits MUST be merged into the message object using the JSON Merge Patch algorithm in the same order they are defined here. The resulting object MUST be a valid Message Object. /// - public IList Traits { get; set; } = new List(); + public virtual IList Traits { get; set; } = new List(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public bool UnresolvedReference { get; set; } - - /// - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs index 4c3ef683..cd369b60 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs @@ -10,49 +10,27 @@ namespace LEGO.AsyncAPI.Models /// /// Describes a parameter included in a channel name. /// - public class AsyncApiParameter : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyncApiSerializable + public class AsyncApiParameter : IAsyncApiExtensible, IAsyncApiSerializable { /// /// Gets or sets a verbose explanation of the parameter. CommonMark syntax can be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Gets or sets definition of the parameter. /// - public AsyncApiJsonSchema Schema { get; set; } + public virtual AsyncApiJsonSchema Schema { get; set; } /// /// Gets or sets a runtime expression that specifies the location of the parameter value. /// - public string Location { get; set; } + public virtual string Location { get; set; } /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public bool UnresolvedReference { get; set; } - - /// - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs index 475497d2..38607f99 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs @@ -7,7 +7,7 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public class AsyncApiSecurityRequirement : Dictionary>, IAsyncApiSerializable + public class AsyncApiSecurityRequirement : Dictionary>, IAsyncApiSerializable { /// /// Initializes a new instance of the class. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityScheme.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityScheme.cs index 08fd3d67..4c5bb0ff 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityScheme.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityScheme.cs @@ -7,89 +7,60 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public class AsyncApiSecurityScheme : IAsyncApiSerializable, IAsyncApiReferenceable, IAsyncApiExtensible + public class AsyncApiSecurityScheme : IAsyncApiSerializable, IAsyncApiExtensible { /// /// REQUIRED. The type of the security scheme. Valid values are "userPassword", "apiKey", "X509", "symmetricEncryption", "asymmetricEncryption", "httpApiKey", "http", "oauth2", "openIdConnect", "plain", "scramSha256", "scramSha512", and "gssapi". /// - public SecuritySchemeType Type { get; set; } + public virtual SecuritySchemeType Type { get; set; } /// /// A short description for security scheme. CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// REQUIRED. The name of the header, query or cookie parameter to be used. /// - public string Name { get; set; } + public virtual string Name { get; set; } /// /// REQUIRED. The location of the API key. Valid values are "user" and "password" for apiKey and "query", "header" or "cookie" for httpApiKey. /// - public ParameterLocation In { get; set; } + public virtual ParameterLocation In { get; set; } /// /// REQUIRED. The name of the HTTP Authorization scheme to be used /// in the Authorization header as defined in RFC7235. /// - public string Scheme { get; set; } + public virtual string Scheme { get; set; } /// /// A hint to the client to identify how the bearer token is formatted. /// Bearer tokens are usually generated by an authorization server, /// so this information is primarily for documentation purposes. /// - public string BearerFormat { get; set; } + public virtual string BearerFormat { get; set; } /// /// REQUIRED. An object containing configuration information for the flow types supported. /// - public AsyncApiOAuthFlows Flows { get; set; } + public virtual AsyncApiOAuthFlows Flows { get; set; } /// /// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values. /// - public Uri OpenIdConnectUrl { get; set; } + public virtual Uri OpenIdConnectUrl { get; set; } /// /// Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); - - /// - /// Indicates if object is populated with data or is just a reference to the data. - /// - public bool UnresolvedReference { get; set; } - - /// - /// Reference object. - /// - public AsyncApiReference Reference { get; set; } - - /// - /// Serialize to Async Api v2.3. - /// - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Serialize to AsyncApi V2 document without using reference. /// - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); @@ -143,47 +114,5 @@ public void SerializeV2WithoutReference(IAsyncApiWriter writer) writer.WriteEndObject(); } - - /// - /// Arbitrarily chooses one object from the - /// to populate in V2 security scheme. - /// - private static void WriteOAuthFlowForV2(IAsyncApiWriter writer, AsyncApiOAuthFlows flows) - { - if (flows != null) - { - if (flows.Implicit != null) - { - WriteOAuthFlowForV2(writer, AsyncApiConstants.Implicit, flows.Implicit); - } - else if (flows.Password != null) - { - WriteOAuthFlowForV2(writer, AsyncApiConstants.Password, flows.Password); - } - else if (flows.ClientCredentials != null) - { - WriteOAuthFlowForV2(writer, AsyncApiConstants.Application, flows.ClientCredentials); - } - else if (flows.AuthorizationCode != null) - { - WriteOAuthFlowForV2(writer, AsyncApiConstants.AccessCode, flows.AuthorizationCode); - } - } - } - - private static void WriteOAuthFlowForV2(IAsyncApiWriter writer, string flowValue, AsyncApiOAuthFlow flow) - { - // flow - writer.WriteOptionalProperty(AsyncApiConstants.Flow, flowValue); - - // authorizationUrl - writer.WriteOptionalProperty(AsyncApiConstants.AuthorizationUrl, flow.AuthorizationUrl?.ToString()); - - // tokenUrl - writer.WriteOptionalProperty(AsyncApiConstants.TokenUrl, flow.TokenUrl?.ToString()); - - // scopes - writer.WriteOptionalMap(AsyncApiConstants.Scopes, flow.Scopes, (w, s) => w.WriteValue(s)); - } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiServerVariable.cs b/src/LEGO.AsyncAPI/Models/AsyncApiServerVariable.cs index ed02634a..57069b99 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiServerVariable.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiServerVariable.cs @@ -10,52 +10,32 @@ namespace LEGO.AsyncAPI.Models /// /// An object representing a Server Variable for server URL template substitution. /// - public class AsyncApiServerVariable : IAsyncApiSerializable, IAsyncApiExtensible, IAsyncApiReferenceable + public class AsyncApiServerVariable : IAsyncApiSerializable, IAsyncApiExtensible { /// /// An enumeration of string values to be used if the substitution options are from a limited set. /// - public IList Enum { get; set; } = new List(); + public virtual IList Enum { get; set; } = new List(); /// /// The default value to use for substitution, and to send, if an alternate value is not supplied. /// - public string Default { get; set; } + public virtual string Default { get; set; } /// /// An optional description for the server variable. CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// An array of examples of the server variable. /// - public IList Examples { get; set; } = new List(); + public virtual IList Examples { get; set; } = new List(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs index b5480e51..54e344e4 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs @@ -9,7 +9,7 @@ public interface IAsyncApiReferenceable : IAsyncApiSerializable /// /// Indicates if object is populated with data or is just a reference to the data. /// - bool UnresolvedReference { get; set; } + bool UnresolvedReference { get; } /// /// Reference object. diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs new file mode 100644 index 00000000..56d393fe --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs @@ -0,0 +1,57 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiChannelReference : AsyncApiChannel, IAsyncApiReferenceable + { + private AsyncApiChannel target; + + private AsyncApiChannel Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiChannelReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Channel); + } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override IList Servers { get => this.Target?.Servers; set => this.Target.Servers = value; } + + public override AsyncApiOperation Subscribe { get => this.Target?.Subscribe; set => this.Target.Subscribe = value; } + + public override AsyncApiOperation Publish { get => this.Target?.Publish; set => this.Target.Publish = value; } + + public override IDictionary Parameters { get => this.Target?.Parameters; set => this.Target.Parameters = value; } + + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs new file mode 100644 index 00000000..f3edde84 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs @@ -0,0 +1,52 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a correlation ID this application MAY use. + /// + public class AsyncApiCorrelationIdReference : AsyncApiCorrelationId, IAsyncApiReferenceable + { + private AsyncApiCorrelationId target; + + private AsyncApiCorrelationId Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiCorrelationIdReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.CorrelationId); + } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override string Location { get => this.Target?.Location; set => this.Target.Location = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs new file mode 100644 index 00000000..0c5d6cb4 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -0,0 +1,70 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a message this application MAY use. + /// + public class AsyncApiMessageReference : AsyncApiMessage, IAsyncApiReferenceable + { + private AsyncApiMessage target; + + private AsyncApiMessage Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiMessageReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Message); + } + + public override string MessageId { get => this.Target?.MessageId; set => this.Target.MessageId = value; } + public override AsyncApiJsonSchema Headers { get => this.Target?.Headers; set => this.Target.Headers = value; } + public override IAsyncApiMessagePayload Payload { get => this.Target?.Payload; set => this.Target.Payload = value; } + public override AsyncApiCorrelationId CorrelationId { get => this.Target?.CorrelationId; set => this.Target.CorrelationId = value; } + public override string SchemaFormat { get => this.Target?.SchemaFormat; set => this.Target.SchemaFormat = value; } + public override string ContentType { get => this.Target?.ContentType; set => this.Target.ContentType = value; } + public override string Name { get => this.Target?.Name; set => this.Target.Name = value; } + + public override string Title { get => this.Target?.Title; set => this.Target.Title = value; } + + public override string Summary { get => this.Target?.Summary; set => this.Target.Summary = value; } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override IList Tags { get => this.Target?.Tags; set => this.Target.Tags = value; } + + public override AsyncApiExternalDocumentation ExternalDocs { get => this.Target?.ExternalDocs; set => this.Target.ExternalDocs = value; } + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } + + public override IList Examples { get => this.Target?.Examples; set => this.Target.Examples = value; } + + public override IList Traits { get => this.Target?.Traits; set => this.Target.Traits = value; } + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs new file mode 100644 index 00000000..d0a9a677 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs @@ -0,0 +1,54 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a parameter this application MAY use. + /// + public class AsyncApiParameterReference : AsyncApiParameter, IAsyncApiReferenceable + { + private AsyncApiParameter target; + + private AsyncApiParameter Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiParameterReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Parameter); + } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override AsyncApiJsonSchema Schema { get => this.Target?.Schema; set => this.Target.Schema = value; } + + public override string Location { get => this.Target?.Location; set => this.Target.Location = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs new file mode 100644 index 00000000..0aeec371 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -0,0 +1,64 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a security scheme this application MAY use. + /// + public class AsyncApiSecuritySchemeReference : AsyncApiSecurityScheme, IAsyncApiReferenceable + { + private AsyncApiSecurityScheme target; + + private AsyncApiSecurityScheme Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiSecuritySchemeReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.SecurityScheme); + } + + public override SecuritySchemeType Type { get => this.Target.Type; set => this.Target.Type = value; } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override string Name { get => this.Target?.Name; set => this.Target.Name = value; } + + public override ParameterLocation In { get => this.Target.In; set => this.Target.In = value; } + + public override string Scheme { get => this.Target?.Scheme; set => this.Target.Scheme = value; } + + public override string BearerFormat { get => this.Target?.BearerFormat; set => this.Target.BearerFormat = value; } + + public override AsyncApiOAuthFlows Flows { get => this.Target?.Flows; set => this.Target.Flows = value; } + + public override Uri OpenIdConnectUrl { get => this.Target?.OpenIdConnectUrl; set => this.Target.OpenIdConnectUrl = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs index ec1bcc4a..46e81e9f 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -28,26 +28,27 @@ public AsyncApiServerReference(string reference) this.Reference = new AsyncApiReference(reference, ReferenceType.Server); } - public override string Url { get => this.Target.Url; set => this.Target.Url = value; } + public override string Url { get => this.Target?.Url; set => this.Target.Url = value; } - public override string Protocol { get => this.Target.Protocol; set => this.Target.Protocol = value; } + public override string Protocol { get => this.Target?.Protocol; set => this.Target.Protocol = value; } - public override string ProtocolVersion { get => this.Target.ProtocolVersion; set => this.Target.ProtocolVersion = value; } + public override string ProtocolVersion { get => this.Target?.ProtocolVersion; set => this.Target.ProtocolVersion = value; } - public override string Description { get => this.Target.Description; set => this.Target.Description = value; } + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } - public override IDictionary Variables { get => this.Target.Variables; set => this.Target.Variables = value; } + public override IDictionary Variables { get => this.Target?.Variables; set => this.Target.Variables = value; } - public override IList Security { get => this.Target.Security; set => this.Target.Security = value; } + public override IList Security { get => this.Target?.Security; set => this.Target.Security = value; } - public override IList Tags { get => this.Target.Tags; set => this.Target.Tags = value; } + public override IList Tags { get => this.Target?.Tags; set => this.Target.Tags = value; } - public override AsyncApiBindings Bindings { get => this.Target.Bindings; set => this.Target.Bindings = value; } + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } - public override IDictionary Extensions { get => this.Target.Extensions; set => this.Target.Extensions = value; } + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } public AsyncApiReference Reference { get; set; } - public bool UnresolvedReference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } public override void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs new file mode 100644 index 00000000..6e9041ac --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs @@ -0,0 +1,55 @@ +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a server variable this application MAY use. + /// + public class AsyncApiServerVariableReference : AsyncApiServerVariable, IAsyncApiReferenceable + { + private AsyncApiServerVariable target; + + private AsyncApiServerVariable Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiServerVariableReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.ServerVariable); + } + public override IList Enum { get => this.Target?.Enum; set => this.Target.Enum = value; } + + public override string Default { get => this.Target?.Default; set => this.Target.Default = value; } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override IList Examples { get => this.Target?.Examples; set => this.Target.Examples = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 10a140db..211b237b 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -221,8 +221,9 @@ internal void Walk(AsyncApiOAuthFlow oAuthFlow) internal void Walk(AsyncApiSecurityScheme securityScheme, bool isComponent = false) { - if (securityScheme == null || this.ProcessAsReference(securityScheme, isComponent)) + if (securityScheme is AsyncApiSecuritySchemeReference) { + this.Walk(securityScheme as IAsyncApiReferenceable); return; } @@ -270,8 +271,9 @@ internal void Walk(AsyncApiExternalDocumentation externalDocs) internal void Walk(AsyncApiChannel channel, bool isComponent = false) { - if (channel == null || this.ProcessAsReference(channel, isComponent)) + if (channel is AsyncApiChannelReference) { + this.Walk(channel as IAsyncApiReferenceable); return; } @@ -311,8 +313,9 @@ private void Walk(IDictionary parameters) internal void Walk(AsyncApiParameter parameter, bool isComponent = false) { - if (parameter == null || this.ProcessAsReference(parameter, isComponent)) + if (parameter is AsyncApiParameterReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -529,8 +532,9 @@ internal void Walk(AsyncApiOperationTrait trait, bool isComponent = false) internal void Walk(AsyncApiMessage message, bool isComponent = false) { - if (message == null || this.ProcessAsReference(message, isComponent)) + if (message is AsyncApiMessageReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -775,8 +779,9 @@ internal void Walk(IDictionary anys) internal void Walk(AsyncApiCorrelationId correlationId, bool isComponent = false) { - if (correlationId == null || this.ProcessAsReference(correlationId, isComponent)) + if (correlationId is AsyncApiCorrelationIdReference) { + this.Walk(correlationId as IAsyncApiReferenceable); return; } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 98f0bfe1..84f38d0f 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -332,14 +332,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "lightMeasured", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/lightMeasured"), }, }, }) @@ -350,14 +343,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Parameters = new Dictionary { { - "streetlightId", new AsyncApiParameter() - { - Reference = new AsyncApiReference() - { - Id = "streetlightId", - Type = ReferenceType.Parameter, - }, - } + "streetlightId", new AsyncApiParameterReference("#/components/parameters/streetlightId") }, }, Subscribe = new AsyncApiOperation() @@ -376,14 +362,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "turnOnOff", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/turnOnOff") }, }, }) @@ -1305,14 +1284,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() }; doc.Channels.Add( "testChannel", - new AsyncApiChannel - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Channel, - Id = "otherchannel", - }, - }); + new AsyncApiChannelReference("#/components/channels/otherchannel")); var actual = doc.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); var settings = new AsyncApiReaderSettings(); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index 4fbc2512..97c37f72 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs @@ -30,7 +30,7 @@ public void AsyncApiChannel_WithInlineParameter_DoesNotCreateReference() """; var channel = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _ ); - channel.Parameters.First().Value.Reference.Should().BeNull(); + (channel.Parameters.First().Value as AsyncApiParameterReference).Reference.Should().BeNull(); } [Test] diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index f87a89a1..43997a7d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -185,7 +185,7 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var channel = deserialized.Channels.First().Value; + var channel = deserialized.Channels.First().Value as AsyncApiChannelReference; channel.UnresolvedReference.Should().BeFalse(); channel.Description.Should().Be("customDescription"); @@ -251,7 +251,7 @@ public void AsyncApiDocument_WithExternalReferenceOnlySetToResolveInternalRefere // Assert diagnostic.Errors.Should().BeEmpty(); - var channel = deserialized.Channels.First().Value; + var channel = deserialized.Channels.First().Value as AsyncApiChannelReference; channel.UnresolvedReference.Should().BeTrue(); channel.Description.Should().BeNull(); From c54b76b643c5fe9a9ca556b028ebd9418cbf694b Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 27 Jan 2025 14:18:50 +0100 Subject: [PATCH 06/20] fix reference id to be whole fragments also fixed up external references and streams --- .../AsyncApiJsonDocumentReader.cs | 65 +++-- .../AsyncApiReferenceHostDocumentResolver.cs | 220 ----------------- .../AsyncApiTextReader.cs | 2 +- .../Interface/IStreamLoader.cs | 1 + .../ParseNodes/MapNode.cs | 6 +- ...ltStreamLoader.cs => DefaultFileLoader.cs} | 18 +- ...AsyncApiSecurityRequirementDeserializer.cs | 12 +- .../V2/AsyncApiV2VersionService.cs | 1 - src/LEGO.AsyncAPI.Readers/YamlConverter.cs | 24 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 17 +- .../Models/AsyncApiComponents.cs | 16 +- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 4 +- .../Models/AsyncApiMessageTrait.cs | 3 +- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 79 ++---- .../AsyncApiReferenceEqualityComparer.cs | 4 +- .../Models/AsyncApiSecurityRequirement.cs | 2 +- .../References/AsyncApiMessageReference.cs | 14 +- .../AsyncApiSecuritySchemeReference.cs | 8 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 5 + .../AsyncApiDocumentV2Tests.cs | 98 ++------ .../AsyncApiReaderTests.cs | 2 +- .../LEGO.AsyncAPI.Tests.csproj | 2 +- .../Models/AsyncApiChannel_Should.cs | 4 +- .../Models/AsyncApiReference_Should.cs | 233 ++++++++++++------ .../Models/AsyncApiSchema_Should.cs | 8 +- .../AsyncApiSecurityRequirement_Should.cs | 2 +- .../Models/AsyncApiServer_Should.cs | 11 +- test/LEGO.AsyncAPI.Tests/ReferenceTests.cs | 141 ----------- 28 files changed, 337 insertions(+), 665 deletions(-) rename src/LEGO.AsyncAPI.Readers/Services/{DefaultStreamLoader.cs => DefaultFileLoader.cs} (74%) delete mode 100644 test/LEGO.AsyncAPI.Tests/ReferenceTests.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 65993632..068b6b42 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -10,6 +10,7 @@ namespace LEGO.AsyncAPI.Readers using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; + using Json.More; using Json.Pointer; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; @@ -20,6 +21,7 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Services; using LEGO.AsyncAPI.Validations; + using YamlDotNet.RepresentationModel; /// /// Service class for converting contents of TextReader into AsyncApiDocument instances. @@ -211,7 +213,7 @@ private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable, AsyncApiDocument hostDocument) { - var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); var collector = new AsyncApiRemoteReferenceCollector(hostDocument); var walker = new AsyncApiWalker(collector); walker.Walk(serializable); @@ -232,10 +234,11 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS } else { - stream = loader.Load(new Uri(reference.Reference.Reference, UriKind.RelativeOrAbsolute)); + stream = loader.Load(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute)); + this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream); } - var component = this.ResolveStream(stream, reference, diagnostic); + var component = this.ResolveArtifactReferences(stream, reference, diagnostic); if (component == null) { diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); @@ -252,12 +255,34 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS } } - private IAsyncApiSerializable ResolveStream(Stream input, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + private JsonNode ReadToJson(Stream stream) { - var json = JsonNode.Parse(input); + if (stream != null) + { + var reader = new StreamReader(stream); + var yamlStream = new YamlStream(); + yamlStream.Load(reader); + return yamlStream.Documents.First().ToJsonNode(this.settings.CultureInfo); + } + + return default; + } + + private IAsyncApiSerializable ResolveArtifactReferences(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + { + JsonNode json = null; + try + { + json = this.ReadToJson(stream); + } + catch + { + diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference: '{reference.Reference.Reference}'")); + } + if (reference.Reference.IsFragment) { - var pointer = JsonPointer.Parse(reference.Reference.Id); + var pointer = JsonPointer.Parse(reference.Reference.FragmentId); if (pointer.TryEvaluate(json, out var pointerResult)) { json = pointerResult; @@ -269,53 +294,55 @@ private IAsyncApiSerializable ResolveStream(Stream input, IAsyncApiReferenceable } } + AsyncApiDiagnostic fragmentDiagnostic = new AsyncApiDiagnostic(); IAsyncApiSerializable result = null; switch (reference.Reference.Type) { case ReferenceType.Schema: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var streamDiagnostics); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.Server: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.Channel: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.Message: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.SecurityScheme: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.Parameter: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.CorrelationId: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.OperationTrait: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.MessageTrait: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.ServerBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.ChannelBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.OperationBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.MessageBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out var _); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; default: diagnostic.Errors.Add(new AsyncApiError(reference.Reference.Reference, "Could not resolve reference.")); break; } + diagnostic.Append(fragmentDiagnostic); return result; } } diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs index 5f99ece0..c170207b 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs @@ -2,21 +2,10 @@ namespace LEGO.AsyncAPI.Readers { - using System; using System.Collections.Generic; - using System.Linq; - using System.Text.Json.Nodes; - using System.Threading; - using System.Threading.Tasks; - using LEGO.AsyncAPI.Exceptions; - using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Readers.Interface; - using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Services; - using LEGO.AsyncAPI.Validations; - internal class AsyncApiReferenceHostDocumentResolver : AsyncApiVisitorBase { @@ -42,216 +31,7 @@ public override void Visit(IAsyncApiReferenceable referenceable) if (referenceable.Reference != null) { referenceable.Reference.HostDocument = this.currentDocument; - this.currentDocument.Workspace.RegisterReference(referenceable); } - } } - //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); - //} - - ///// - ///// Resolve all references used in an operation. - ///// - //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); - - // // #ToFix Resolve references correctly - // if (message.Payload is AsyncApiJsonSchemaPayload) - // { - // this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, 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); - //} - - ///// - ///// Resolve all references to SecuritySchemes. - ///// - //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); - // } - // }); - // } - //} - - ///// - ///// Resolve all references to parameters. - ///// - //public override void Visit(IList parameters) - //{ - // this.ResolveList(parameters); - //} - - ///// - ///// Resolve all references used in a parameter. - ///// - //public override void Visit(AsyncApiParameter parameter) - //{ - // this.ResolveObject(parameter.Schema, r => parameter.Schema = r); - //} - - ///// - ///// Resolve all references used in a schema. - ///// - //public override void Visit(AsyncApiJsonSchema 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 entity, Action assign) - // where T : class, IAsyncApiReferenceable, new() - //{ - // if (entity == null) - // { - // return; - // } - - // if (this.IsUnresolvedReference(entity)) - // { - // assign(this.ResolveReference(entity.Reference)); - // } - //} - - //private void ResolveList(IList 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(entity.Reference); - // } - // } - //} - - //private void ResolveMap(IDictionary 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(entity.Reference); - // } - // } - //} - - //private T ResolveReference(AsyncApiReference reference) - // where T : class, IAsyncApiReferenceable, new() - //{ - // // external references are resolved by the AsyncApiExternalReferenceResolver - // if (reference.IsExternal) - // { - // return new() - // { - // UnresolvedReference = true, - // Reference = reference, - // }; - // } - - // try - // { - // var resolvedReference = this.currentDocument.ResolveReference(reference); - // if (resolvedReference == null) - // { - // throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'."); - // } - - // return resolvedReference; - // } - // catch (AsyncApiException ex) - // { - // this.errors.Add(new AsyncApiReferenceError(ex)); - // return null; - // } - //} - - //private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference) - //{ - // return (possibleReference != null && possibleReference.UnresolvedReference); - //} - //} } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs index 8d1eafc6..78b6973a 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs @@ -122,7 +122,7 @@ static JsonNode LoadYamlDocument(TextReader input, AsyncApiReaderSettings settin { var yamlStream = new YamlStream(); yamlStream.Load(input); - return yamlStream.Documents.First().ToJsonNode(settings); + return yamlStream.Documents.First().ToJsonNode(settings.CultureInfo); } } } diff --git a/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs index d0daca7c..14da05c9 100644 --- a/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs @@ -4,6 +4,7 @@ namespace LEGO.AsyncAPI.Readers.Interface { using System; using System.IO; + using System.Text.Json.Nodes; using System.Threading.Tasks; public interface IStreamLoader diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs index 76a98a5d..8f392f35 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs @@ -116,11 +116,7 @@ public override Dictionary CreateMapWithReference( if (entry.value.Reference == null) { - entry.value.Reference = new AsyncApiReference() - { - Type = referenceType, - Id = entry.key, - }; + entry.value.Reference = new AsyncApiReference(entry.key, referenceType); } } finally diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultFileLoader.cs similarity index 74% rename from src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs rename to src/LEGO.AsyncAPI.Readers/Services/DefaultFileLoader.cs index 98b710c7..aaaf639b 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/DefaultFileLoader.cs @@ -1,4 +1,4 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Readers.Services { @@ -11,7 +11,13 @@ namespace LEGO.AsyncAPI.Readers.Services internal class DefaultStreamLoader : IStreamLoader { - private static readonly HttpClient httpClient = new HttpClient(); + private static readonly HttpClient HttpClient = new HttpClient(); + private readonly AsyncApiReaderSettings settings; + + public DefaultStreamLoader(AsyncApiReaderSettings settings) + { + this.settings = settings; + } public Stream Load(Uri uri) { @@ -21,9 +27,11 @@ public Stream Load(Uri uri) { case "file": return File.OpenRead(uri.AbsolutePath); + break; case "http": case "https": - return httpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); + return HttpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); + break; default: throw new ArgumentException("Unsupported scheme"); } @@ -43,9 +51,11 @@ public async Task LoadAsync(Uri uri) { case "file": return File.OpenRead(uri.AbsolutePath); + break; case "http": case "https": - return await httpClient.GetStreamAsync(uri); + return await HttpClient.GetStreamAsync(uri); + break; default: throw new ArgumentException("Unsupported scheme"); } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecurityRequirementDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecurityRequirementDeserializer.cs index e3574f6a..f9f02907 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecurityRequirementDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSecurityRequirementDeserializer.cs @@ -36,19 +36,11 @@ public static AsyncApiSecurityRequirement LoadSecurityRequirement(ParseNode node return securityRequirement; } - private static AsyncApiSecurityScheme LoadSecuritySchemeByReference( + private static AsyncApiSecuritySchemeReference LoadSecuritySchemeByReference( ParsingContext context, string schemeName) { - var securitySchemeObject = new AsyncApiSecurityScheme - { - UnresolvedReference = true, - Reference = new AsyncApiReference - { - Id = schemeName, - Type = ReferenceType.SecurityScheme, - }, - }; + var securitySchemeObject = new AsyncApiSecuritySchemeReference(schemeName); return securitySchemeObject; } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 21f3ffc0..ae7cc530 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -9,7 +9,6 @@ namespace LEGO.AsyncAPI.Readers.V2 using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; using LEGO.AsyncAPI.Readers.ParseNodes; - using LEGO.AsyncAPI.Writers; internal class AsyncApiV2VersionService : IAsyncApiVersionService { diff --git a/src/LEGO.AsyncAPI.Readers/YamlConverter.cs b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs index 648ee50a..29225f06 100644 --- a/src/LEGO.AsyncAPI.Readers/YamlConverter.cs +++ b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs @@ -10,51 +10,51 @@ namespace LEGO.AsyncAPI.Readers internal static class YamlConverter { - public static JsonNode ToJsonNode(this YamlDocument yamlDocument, AsyncApiReaderSettings settings) + public static JsonNode ToJsonNode(this YamlDocument yamlDocument, CultureInfo cultureInfo) { - return yamlDocument.RootNode.ToJsonNode(settings); + return yamlDocument.RootNode.ToJsonNode(cultureInfo); } - public static JsonObject ToJsonObject(this YamlMappingNode yamlMappingNode, AsyncApiReaderSettings settings) + public static JsonObject ToJsonObject(this YamlMappingNode yamlMappingNode, CultureInfo cultureInfo) { var node = new JsonObject(); foreach (var keyValuePair in yamlMappingNode) { var key = ((YamlScalarNode)keyValuePair.Key).Value!; - node[key] = keyValuePair.Value.ToJsonNode(settings); + node[key] = keyValuePair.Value.ToJsonNode(cultureInfo); } return node; } - public static JsonArray ToJsonArray(this YamlSequenceNode yaml, AsyncApiReaderSettings settings) + public static JsonArray ToJsonArray(this YamlSequenceNode yaml, CultureInfo cultureInfo) { var node = new JsonArray(); foreach (var value in yaml) { - node.Add(value.ToJsonNode(settings)); + node.Add(value.ToJsonNode(cultureInfo)); } return node; } - public static JsonNode ToJsonNode(this YamlNode yaml, AsyncApiReaderSettings settings) + public static JsonNode ToJsonNode(this YamlNode yaml, CultureInfo cultureInfo) { return yaml switch { - YamlMappingNode map => map.ToJsonObject(settings), - YamlSequenceNode seq => seq.ToJsonArray(settings), - YamlScalarNode scalar => scalar.ToJsonValue(settings), + YamlMappingNode map => map.ToJsonObject(cultureInfo), + YamlSequenceNode seq => seq.ToJsonArray(cultureInfo), + YamlScalarNode scalar => scalar.ToJsonValue(cultureInfo), _ => throw new NotSupportedException("This yaml isn't convertible to JSON"), }; } - private static JsonValue ToJsonValue(this YamlScalarNode yaml, AsyncApiReaderSettings settings) + private static JsonValue ToJsonValue(this YamlScalarNode yaml, CultureInfo cultureInfo) { switch (yaml.Style) { case ScalarStyle.Plain: - return decimal.TryParse(yaml.Value, NumberStyles.Float, settings.CultureInfo, out var d) + return decimal.TryParse(yaml.Value, NumberStyles.Float, cultureInfo, out var d) ? JsonValue.Create(d) : bool.TryParse(yaml.Value, out var b) ? JsonValue.Create(b) diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index dadb635a..310ddce6 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -5,13 +5,11 @@ namespace LEGO.AsyncAPI using System; using System.Collections.Generic; using System.IO; - using System.Text.Json.Nodes; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; public class AsyncApiWorkspace { - private readonly Dictionary referenceRegistry = new(); private readonly Dictionary artifactsRegistry = new(); private readonly Dictionary resolvedReferenceRegistry = new(); @@ -156,12 +154,6 @@ public bool RegisterComponent(string location, T component) return false; } - public void RegisterReference(IAsyncApiReferenceable reference) - { - var location = reference.Reference.Reference; - this.referenceRegistry[location] = reference; - } - public bool Contains(string location) { var key = this.ToLocationUrl(location); @@ -169,6 +161,7 @@ public bool Contains(string location) } public T ResolveReference(string location) + where T : class { if (string.IsNullOrEmpty(location)) { @@ -178,17 +171,19 @@ public T ResolveReference(string location) var uri = this.ToLocationUrl(location); if (this.resolvedReferenceRegistry.TryGetValue(uri, out var referenceableValue)) { - return (T)referenceableValue; + return referenceableValue as T; } - if (this.artifactsRegistry.TryGetValue(uri, out var stream)) + if (this.artifactsRegistry.TryGetValue(uri, out var json)) { - return (T)(object)stream; + return (T)(object)json; } return default; } + + private Uri ToLocationUrl(string location) { return new(location, UriKind.RelativeOrAbsolute); diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index dece92ee..9ac21433 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -104,7 +104,7 @@ public void SerializeV2(IAsyncApiWriter writer) if (loops.TryGetValue(typeof(AsyncApiJsonSchema), out List schemas)) { var asyncApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.Id); + .ToDictionary(k => k.Reference.FragmentId); writer.WriteOptionalMap( AsyncApiConstants.Schemas, @@ -132,7 +132,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.Schema && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -262,7 +262,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.OperationTrait && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -280,7 +280,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.MessageTrait && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -298,7 +298,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.ServerBindings && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -316,7 +316,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.ChannelBindings && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -334,7 +334,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.OperationBindings && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } @@ -352,7 +352,7 @@ public void SerializeV2(IAsyncApiWriter writer) { if (component.Reference != null && component.Reference.Type == ReferenceType.MessageBindings && - component.Reference.Id == key) + component.Reference.FragmentId == key) { component.SerializeV2WithoutReference(w); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index fe8e101c..1b66525f 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -13,7 +13,7 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable { - public AsyncApiWorkspace Workspace { get; set; } + internal AsyncApiWorkspace Workspace { get; set; } /// /// REQUIRED. Specifies the AsyncAPI Specification version being used. @@ -110,7 +110,7 @@ public void SerializeV2(IAsyncApiWriter writer) } internal T? ResolveReference(AsyncApiReference reference) - where T : IAsyncApiSerializable + where T : class, IAsyncApiSerializable { return this.Workspace.ResolveReference(reference.Reference); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs index c14a9a24..2d972363 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs @@ -68,8 +68,7 @@ public class AsyncApiMessageTrait : IAsyncApiExtensible, IAsyncApiReferenceable, /// /// additional external documentation for this message. /// - public AsyncApiExternalDocumentation ExternalDocs { get; set; } - + public AsyncApiExternalDocumentation ExternalDocs { get; set; } /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the message. /// diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 8093bc62..3e5fc986 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -13,6 +13,8 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiReference : IAsyncApiSerializable { + private string originalString; + public AsyncApiReference() { } @@ -24,13 +26,14 @@ public AsyncApiReference(string reference, ReferenceType? type) throw new AsyncApiException($"The reference string '{reference}' has invalid format."); } + this.originalString = reference; var segments = reference.Split('#'); if (segments.Length == 1) { if (type == ReferenceType.SecurityScheme) { - this.Type = type; - this.Id = reference; + this.Type = ReferenceType.SecurityScheme; + this.FragmentId = reference; return; } @@ -41,51 +44,36 @@ public AsyncApiReference(string reference, ReferenceType? type) } else if (segments.Length == 2) { - // Local components reference - if (reference.StartsWith("#")) + if (reference.StartsWith("#")) // is local reference { var localSegments = reference.Split('/'); if (localSegments.Length == 4) { - if (localSegments[1] == "components") + if (localSegments[1] == "components") // is local components reference { - var referenceType = localSegments[2].GetEnumFromDisplayName(); - - this.Type = referenceType; - this.Id = localSegments[3]; - this.IsFragment = true; - return; + var referencedType = localSegments[2].GetEnumFromDisplayName(); + if (type == null || type == ReferenceType.None) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new AsyncApiException("Referenced type mismatch"); + } + } } } - - throw new AsyncApiException($"The reference string '{reference}' has invalid format."); } - - var id = segments[1]; - if (id.StartsWith("/components/")) + else { - var localSegments = segments[1].Split('/'); - var referencedType = localSegments[2].GetEnumFromDisplayName(); - if (type == null) - { - type = referencedType; - } - else - { - if (type != referencedType) - { - throw new AsyncApiException("Referenced type mismatch"); - } - } - - id = localSegments[3]; + this.ExternalResource = segments[0]; } - this.IsFragment = true; - this.ExternalResource = segments[0]; this.Type = type; - this.Id = id; + this.FragmentId = segments[1]; return; } @@ -113,7 +101,7 @@ public string ExternalResource /// /// Gets or sets the identifier of the reusable component of one particular ReferenceType. /// - public string Id { get; set; } + public string FragmentId { get; set; } /// /// Gets or sets the AsyncApiDocument that is hosting the AsyncApiReference instance. This is used to enable dereferencing the reference. @@ -123,7 +111,7 @@ public string ExternalResource /// /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment. /// - public bool IsFragment { get; set; } = false; + public bool IsFragment => this.FragmentId != null; /// /// Gets a flag indicating whether this reference is an external reference. @@ -137,22 +125,7 @@ public string Reference { get { - if (this.IsExternal) - { - return this.GetExternalReferenceV2(); - } - - if (!this.Type.HasValue) - { - throw new ArgumentNullException(nameof(this.Type)); - } - - //if (this.Type == ReferenceType.SecurityScheme) - //{ - // return this.Id; - //} - - return "#/components/" + this.Type.GetDisplayName() + "/" + this.Id; + return this.originalString; } } @@ -184,7 +157,7 @@ public void SerializeV2(IAsyncApiWriter writer) private string GetExternalReferenceV2() { - return this.ExternalResource + (this.Id != null ? "/#/" + this.Id : string.Empty); + return this.ExternalResource + (this.FragmentId != null ? "#" + this.FragmentId : string.Empty); } public void Write(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReferenceEqualityComparer.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReferenceEqualityComparer.cs index cbdf812c..da7abd22 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReferenceEqualityComparer.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReferenceEqualityComparer.cs @@ -31,7 +31,7 @@ public bool Equals(IAsyncApiReferenceable x, IAsyncApiReferenceable y) return false; } - return x.Reference.Id == y.Reference.Id; + return x.Reference.FragmentId == y.Reference.FragmentId; } /// @@ -39,7 +39,7 @@ public bool Equals(IAsyncApiReferenceable x, IAsyncApiReferenceable y) /// public int GetHashCode(IAsyncApiReferenceable obj) { - return obj?.Reference?.Id == null ? 0 : obj.Reference.Id.GetHashCode(); + return obj?.Reference?.FragmentId == null ? 0 : obj.Reference.FragmentId.GetHashCode(); } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs index 38607f99..95e93a89 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs @@ -45,7 +45,7 @@ public void SerializeV2(IAsyncApiWriter writer) } // securityScheme.SerializeV2(writer); - writer.WritePropertyName(securityScheme.Reference.Id); + writer.WritePropertyName(securityScheme.Reference.FragmentId); writer.WriteStartArray(); foreach (var scope in scopes) diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs index 0c5d6cb4..944e867d 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -27,11 +27,17 @@ public AsyncApiMessageReference(string reference) } public override string MessageId { get => this.Target?.MessageId; set => this.Target.MessageId = value; } + public override AsyncApiJsonSchema Headers { get => this.Target?.Headers; set => this.Target.Headers = value; } + public override IAsyncApiMessagePayload Payload { get => this.Target?.Payload; set => this.Target.Payload = value; } + public override AsyncApiCorrelationId CorrelationId { get => this.Target?.CorrelationId; set => this.Target.CorrelationId = value; } + public override string SchemaFormat { get => this.Target?.SchemaFormat; set => this.Target.SchemaFormat = value; } + public override string ContentType { get => this.Target?.ContentType; set => this.Target.ContentType = value; } + public override string Name { get => this.Target?.Name; set => this.Target.Name = value; } public override string Title { get => this.Target?.Title; set => this.Target.Title = value; } @@ -41,14 +47,16 @@ public AsyncApiMessageReference(string reference) public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } public override IList Tags { get => this.Target?.Tags; set => this.Target.Tags = value; } - + public override AsyncApiExternalDocumentation ExternalDocs { get => this.Target?.ExternalDocs; set => this.Target.ExternalDocs = value; } + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } - + public override IList Examples { get => this.Target?.Examples; set => this.Target.Examples = value; } public override IList Traits { get => this.Target?.Traits; set => this.Target.Traits = value; } - public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } public AsyncApiReference Reference { get; set; } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs index 0aeec371..c8cc559f 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -33,13 +33,13 @@ public AsyncApiSecuritySchemeReference(string reference) public override string Name { get => this.Target?.Name; set => this.Target.Name = value; } public override ParameterLocation In { get => this.Target.In; set => this.Target.In = value; } - + public override string Scheme { get => this.Target?.Scheme; set => this.Target.Scheme = value; } - + public override string BearerFormat { get => this.Target?.BearerFormat; set => this.Target.BearerFormat = value; } - + public override AsyncApiOAuthFlows Flows { get => this.Target?.Flows; set => this.Target.Flows = value; } - + public override Uri OpenIdConnectUrl { get => this.Target?.OpenIdConnectUrl; set => this.Target.OpenIdConnectUrl = value; } public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 211b237b..b9f04140 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -881,6 +881,11 @@ internal void Walk(AsyncApiSecurityRequirement securityRequirement) } this.visitor.Visit(securityRequirement); + foreach (var item in securityRequirement.Keys) + { + this.Walk(item as IAsyncApiReferenceable); + } + this.Walk(securityRequirement as IAsyncApiExtensible); } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 84f38d0f..e5695743 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -226,14 +226,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() new AsyncApiSecurityRequirement { { - new AsyncApiSecurityScheme() - { - Reference = new AsyncApiReference() - { - Id = "saslScram", - Type = ReferenceType.SecurityScheme, - }, - }, new List() + new AsyncApiSecuritySchemeReference("saslScram"), new List() }, }, }, @@ -266,14 +259,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() new AsyncApiSecurityRequirement { { - new AsyncApiSecurityScheme() - { - Reference = new AsyncApiReference() - { - Id = "certs", - Type = ReferenceType.SecurityScheme, - }, - }, new List() + new AsyncApiSecuritySchemeReference("certs"), new List() }, }, }, @@ -305,14 +291,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Parameters = new Dictionary { { - "streetlightId", new AsyncApiParameter() - { - Reference = new AsyncApiReference() - { - Id = "streetlightId", - Type = ReferenceType.Parameter, - }, - } + "streetlightId", new AsyncApiParameterReference("#/components/parameters/streetlightId") }, }, Publish = new AsyncApiOperation() @@ -325,7 +304,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { Reference = new AsyncApiReference() { - Id = "kafka", + FragmentId = "kafka", Type = ReferenceType.OperationTrait, }, }, @@ -355,7 +334,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { Reference = new AsyncApiReference() { - Id = "kafka", + FragmentId = "kafka", Type = ReferenceType.OperationTrait, }, }, @@ -373,14 +352,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Parameters = new Dictionary { { - "streetlightId", new AsyncApiParameter() - { - Reference = new AsyncApiReference() - { - Id = "streetlightId", - Type = ReferenceType.Parameter, - }, - } + "streetlightId", new AsyncApiParameterReference("#/components/parameters/streetlightId") }, }, Subscribe = new AsyncApiOperation() @@ -392,18 +364,18 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { Reference = new AsyncApiReference() { - Id = "kafka", + FragmentId = "kafka", Type = ReferenceType.OperationTrait, }, }, }, Message = new List { - new AsyncApiMessage() + new AsyncApiMessageReference("#/components/messages/turnOnOff") { Reference = new AsyncApiReference() { - Id = "turnOnOff", + FragmentId = "turnOnOff", Type = ReferenceType.Message, }, }, @@ -417,14 +389,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Parameters = new Dictionary { { - "streetlightId", new AsyncApiParameter() - { - Reference = new AsyncApiReference() - { - Id = "streetlightId", - Type = ReferenceType.Parameter, - }, - } + "streetlightId", new AsyncApiParameterReference("#/components/parameters/streetlightId") }, }, Subscribe = new AsyncApiOperation() @@ -436,21 +401,14 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { Reference = new AsyncApiReference() { - Id = "kafka", + FragmentId = "kafka", Type = ReferenceType.OperationTrait, }, }, }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "dimLight", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/dimLight"), }, }, }) @@ -467,7 +425,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.MessageTrait, - Id = "commonHeaders", + FragmentId = "commonHeaders", }, }, }, @@ -476,7 +434,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "lightMeasuredPayload", + FragmentId = "lightMeasuredPayload", }, }, }) @@ -492,7 +450,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.MessageTrait, - Id = "commonHeaders", + FragmentId = "commonHeaders", }, }, }, @@ -501,7 +459,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "turnOnOffPayload", + FragmentId = "turnOnOffPayload", }, }, }) @@ -517,7 +475,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.MessageTrait, - Id = "commonHeaders", + FragmentId = "commonHeaders", }, }, }, @@ -526,7 +484,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "dimLightPayload", + FragmentId = "dimLightPayload", }, }, }) @@ -549,7 +507,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "sentAt", + FragmentId = "sentAt", }, } }, @@ -578,7 +536,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "sentAt", + FragmentId = "sentAt", }, } }, @@ -604,7 +562,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.Schema, - Id = "sentAt", + FragmentId = "sentAt", }, } }, @@ -900,14 +858,8 @@ public void SerializeV2_WithFullSpec_Serializes() new AsyncApiSecurityRequirement { { - new AsyncApiSecurityScheme() - { - Reference = new AsyncApiReference() - { - Id = securitySchemeName, - Type = ReferenceType.SecurityScheme, - }, - }, new List + new AsyncApiSecuritySchemeReference(securitySchemeName), + new List { requirementString, } @@ -1230,7 +1182,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.ServerBindings, - Id = "bindings", + FragmentId = "bindings", }, }, }); @@ -1250,7 +1202,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() Reference = new AsyncApiReference() { Type = ReferenceType.ChannelBindings, - Id = "bindings", + FragmentId = "bindings", }, }, } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index 036fae56..ebb7dfc3 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -452,7 +452,7 @@ public void Read_WithWrongReference_AddsError() var reader = new AsyncApiStringReader(); var doc = reader.Read(yaml, out var diagnostic); diagnostic.Errors.Should().NotBeEmpty(); - doc.Channels.Values.First().Publish.Message.First().Should().BeNull(); + doc.Channels.Values.First().Publish.Message.Should().BeEmpty(); } [Test] diff --git a/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj index c70af517..46589db0 100644 --- a/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj +++ b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index 97c37f72..24f37628 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs @@ -29,8 +29,8 @@ public void AsyncApiChannel_WithInlineParameter_DoesNotCreateReference() - 97845c62-329c-4d87-ad24-4f611b909a10 """; - var channel = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _ ); - (channel.Parameters.First().Value as AsyncApiParameterReference).Reference.Should().BeNull(); + var channel = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _); + channel.Parameters.First().Value.Should().NotBeOfType(); } [Test] diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 43997a7d..6b76b18d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -7,14 +7,141 @@ namespace LEGO.AsyncAPI.Tests using System.Linq; using System.Threading.Tasks; using FluentAssertions; - using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; using LEGO.AsyncAPI.Readers.Interface; + using LEGO.AsyncAPI.Readers.V2; using NUnit.Framework; public class AsyncApiReference_Should : TestBase { + [Test] + public void ReferencePointers() + { + var diag = new AsyncApiDiagnostic(); + var versionService = new AsyncApiV2VersionService(diag); + var externalFragment = versionService.ConvertToAsyncApiReference("https://github.com/test/test#whatever", ReferenceType.None); + var internalFragment = versionService.ConvertToAsyncApiReference("#/components/servers/server1", ReferenceType.None); + var localFile = versionService.ConvertToAsyncApiReference("./local/some/folder/whatever.yaml", ReferenceType.None); + var externalFile = versionService.ConvertToAsyncApiReference("https://github.com/test/whatever.yaml", ReferenceType.None); + + externalFragment.ExternalResource.Should().Be("https://github.com/test/test"); + externalFragment.FragmentId.Should().Be("whatever"); + externalFragment.Reference.Should().Be("https://github.com/test/test#whatever"); + externalFragment.IsFragment.Should().BeTrue(); + externalFragment.IsExternal.Should().BeTrue(); + + internalFragment.ExternalResource.Should().BeNull(); + internalFragment.FragmentId.Should().Be("/components/servers/server1"); + internalFragment.Reference.Should().Be("#/components/servers/server1"); + internalFragment.IsFragment.Should().BeTrue(); + internalFragment.IsExternal.Should().BeFalse(); + + localFile.ExternalResource.Should().Be("./local/some/folder/whatever.yaml"); + localFile.Reference.Should().Be("./local/some/folder/whatever.yaml"); + localFile.FragmentId.Should().Be(null); + localFile.IsFragment.Should().BeFalse(); + + externalFile.ExternalResource.Should().Be("https://github.com/test/whatever.yaml"); + externalFile.Reference.Should().Be("https://github.com/test/whatever.yaml"); + externalFile.FragmentId.Should().Be(null); + externalFile.IsFragment.Should().BeFalse(); + } + + [Test] + public void Reference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "https://github.com/test/test#whatever" + } + } + } + """; + + var doc = new AsyncApiStringReader().Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + reference.Reference.ExternalResource.Should().Be("https://github.com/test/test"); + reference.Reference.FragmentId.Should().Be("whatever"); + reference.Reference.HostDocument.Should().Be(doc); + reference.Reference.IsFragment.Should().BeTrue(); + } + + [Test] + public void ResolveExternalReference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "https://gist.githubusercontent.com/VisualBean/7dc9607d735122483e1bb7005ff3ad0e/raw/458729e4d56636ef3bb34762f4a5731ea5043bdf/servers.json#/servers/0" + } + } + } + """; + + var doc = new AsyncApiStringReader(new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences }).Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + //reference.Reference.Id.Should().Be("whatever"); + //reference.Reference.HostDocument.Should().Be(doc); + //reference.Reference.IsFragment.Should().BeTrue(); + } + + + [Test] + public void ServerReference_WithComponentReference_ResolvesReference() + { + var json = + """ + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "#/components/servers/whatever" + } + }, + "components": { + "servers": { + "whatever": { + "url": "wss://production.gigantic-server.com:443", + "protocol": "wss", + "protocolVersion": "1.0.0", + "description": "The production API server", + "variables": { + "username": { + "default": "demo", + "description": "This value is assigned by the service provider" + }, + "password": { + "default": "demo", + "description": "This value is assigned by the service provider" + } + } + } + } + } + } + """; + + var doc = new AsyncApiStringReader().Read(json, out var diag); + var reference = doc.Servers.First().Value as AsyncApiServerReference; + reference.Reference.ExternalResource.Should().BeNull(); + reference.Reference.FragmentId.Should().Be("/components/servers/whatever"); + reference.Reference.HostDocument.Should().Be(doc); + reference.Reference.IsFragment.Should().BeTrue(); + reference.Url.Should().Be("wss://production.gigantic-server.com:443"); + + } + [Test] public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() { @@ -35,7 +162,7 @@ public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/some-resource"); - reference.Id.Should().Be("/path/to/external/fragment"); + reference.FragmentId.Should().Be("/path/to/external/fragment"); reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeTrue(); reference.Type.Should().Be(ReferenceType.Schema); @@ -65,7 +192,7 @@ public void AsyncApiReference_WithFragmentReference_AllowReference() var reference = payload.Reference; reference.Type.Should().Be(ReferenceType.Schema); reference.ExternalResource.Should().Be("/fragments/myFragment"); - reference.Id.Should().BeNull(); + reference.FragmentId.Should().BeNull(); reference.IsFragment.Should().BeFalse(); reference.IsExternal.Should().BeTrue(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); @@ -92,8 +219,8 @@ public void AsyncApiReference_WithInternalComponentReference_AllowReference() var reference = payload.Reference; reference.ExternalResource.Should().BeNull(); reference.Type.Should().Be(ReferenceType.Schema); - reference.Id.Should().Be("test"); - reference.IsFragment.Should().BeFalse(); + reference.FragmentId.Should().Be("test"); + reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeFalse(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); @@ -119,7 +246,7 @@ public void AsyncApiReference_WithExternalFragmentReference_AllowReference() var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("./myjsonfile.json"); - reference.Id.Should().Be("/fragment"); + reference.FragmentId.Should().Be("/fragment"); reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeTrue(); @@ -147,8 +274,8 @@ public void AsyncApiReference_WithExternalComponentReference_AllowReference() var reference = payload.Reference; reference.ExternalResource.Should().Be("./someotherdocument.json"); reference.Type.Should().Be(ReferenceType.Schema); - reference.Id.Should().Be("test"); - reference.IsFragment.Should().BeFalse(); + reference.FragmentId.Should().Be("/components/schemas/test"); + reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeTrue(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); @@ -190,42 +317,11 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference() channel.UnresolvedReference.Should().BeFalse(); channel.Description.Should().Be("customDescription"); channel.Reference.ExternalResource.Should().BeNull(); - channel.Reference.Id.Should().Be("myChannel"); + channel.Reference.FragmentId.Should().Be("/components/channels/myChannel"); channel.Reference.IsExternal.Should().BeFalse(); channel.Reference.Type.Should().Be(ReferenceType.Channel); } - [Test] - public void AsyncApiDocument_WithNoConfiguredExternalReferenceReader_ThrowsError() - { - // Arrange - var actual = """ - asyncapi: 2.6.0 - info: - title: My AsyncAPI Document - version: 1.0.0 - channels: - myChannel: - $ref: http://example.com/channel.json - """; - - var settings = new AsyncApiReaderSettings() - { - ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - }; - var reader = new AsyncApiStringReader(settings); - - // Act - reader.Read(actual, out var diagnostic); - - // Assert - diagnostic.Errors.Count.Should().Be(1); - var error = diagnostic.Errors.First(); - error.Message.Should() - .Be( - "External reference configured in AsyncApi document but no implementation provided for ExternalReferenceReader."); - } - [Test] public void AsyncApiDocument_WithExternalReferenceOnlySetToResolveInternalReferences_DoesNotResolve() { @@ -257,7 +353,7 @@ public void AsyncApiDocument_WithExternalReferenceOnlySetToResolveInternalRefere channel.Description.Should().BeNull(); channel.Reference.ExternalResource.Should().Be("http://example.com/channel.json"); channel.Reference.Type.Should().Be(ReferenceType.Channel); - channel.Reference.Id.Should().BeNull(); + channel.Reference.FragmentId.Should().BeNull(); channel.Reference.IsExternal.Should().BeTrue(); channel.Reference.IsFragment.Should().BeFalse(); } @@ -280,7 +376,7 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/json.json"); - reference.Id.Should().BeNull(); + reference.FragmentId.Should().BeNull(); reference.IsExternal.Should().BeTrue(); reference.IsFragment.Should().BeFalse(); diagnostic.Errors.Should().BeEmpty(); @@ -316,43 +412,13 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var message = doc.Channels["workspace"].Publish.Message.First(); message.Name.Should().Be("Test"); var payload = message.Payload.As(); - payload.Properties.Count.Should().Be(3); + payload.Properties.Count.Should().Be(1); } - - //[Test] - //public void AvroReference_WithExternalResourcesInterface_DeserializesCorrectly() - //{ - // var yaml = """ - // asyncapi: 2.3.0 - // info: - // title: test - // version: 1.0.0 - // channels: - // workspace: - // publish: - // message: - // schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0' - // payload: - // $ref: 'path/to/user-create.avsc/#UserCreate' - // """; - // var settings = new AsyncApiReaderSettings - // { - // ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - // ExternalReferenceReader = new MockExternalAvroReferenceReader(), - // }; - // var reader = new AsyncApiStringReader(settings); - // var doc = reader.Read(yaml, out var diagnostic); - // var payload = doc.Channels["workspace"].Publish.Message.First().Payload; - // payload.Should().BeAssignableTo(typeof(AsyncApiAvroSchemaPayload)); - // var avro = payload as AsyncApiAvroSchemaPayload; - // avro.TryGetAs(out var record); - // record.Name.Should().Be("SomeEvent"); - //} } public class MockLoader : IStreamLoader { - const string Message = + const string Message = """ name: Test title: Test message @@ -363,11 +429,28 @@ public class MockLoader : IStreamLoader $ref: "./some/path/to/schema.yaml" """; + const string Schema = + """ + type: object + properties: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + """; + public Stream Load(Uri uri) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.Write(Message); + if (uri.ToString() == "./some/path/to/external/message.yaml") + { + writer.Write(Message); + } + else + { + writer.Write(Schema); + } writer.Flush(); stream.Position = 0; return stream; diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 30ce0564..0dd091db 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -224,7 +224,7 @@ public class AsyncApiSchema_Should : TestBase Reference = new AsyncApiReference { Type = ReferenceType.Schema, - Id = "schemaObject1", + FragmentId = "schemaObject1", }, }; @@ -426,8 +426,8 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl Required = new HashSet { "testB" }, Properties = new Dictionary { - { "testC", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testC" } } }, - { "testB", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testB" } } }, + { "testC", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testC" } } }, + { "testB", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testB" } } }, }, }, }, @@ -440,7 +440,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl Type = SchemaType.Object, Properties = new Dictionary { - { "testD", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testD" } } }, + { "testD", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testD" } } }, }, }) .WithComponent("testB", new AsyncApiJsonSchema() { Description = "test", Type = SchemaType.Boolean }) diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSecurityRequirement_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSecurityRequirement_Should.cs index b92224df..273e1a7d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSecurityRequirement_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSecurityRequirement_Should.cs @@ -24,7 +24,7 @@ public void SerializeV2_WithNullWriter_Throws() public void SerializeV2_Serializes() { var asyncApiSecurityRequirement = new AsyncApiSecurityRequirement(); - asyncApiSecurityRequirement.Add(new AsyncApiSecurityScheme { Type = SecuritySchemeType.ApiKey }, new List { "string" }); + asyncApiSecurityRequirement.Add(new AsyncApiSecuritySchemeReference("apiKey"), new List { "string" }); var output = asyncApiSecurityRequirement.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiServer_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiServer_Should.cs index 8b14560b..c306fb73 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiServer_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiServer_Should.cs @@ -48,15 +48,8 @@ public void AsyncApiServer_Serializes() new AsyncApiSecurityRequirement { { - new AsyncApiSecurityScheme() - { - Reference = new AsyncApiReference() - { - Id = "schem1", - Type = ReferenceType.SecurityScheme, - }, - Name = "scheme1", - }, new List + new AsyncApiSecuritySchemeReference("schem1") + , new List { "requirement", } diff --git a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs deleted file mode 100644 index 81dc2018..00000000 --- a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) The LEGO Group. All rights reserved. -namespace LEGO.AsyncAPI.Tests -{ - using System; - using System.Collections.Generic; - using System.ComponentModel.DataAnnotations; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using FluentAssertions; - using LEGO.AsyncAPI.Models; - using LEGO.AsyncAPI.Readers; - using LEGO.AsyncAPI.Readers.V2; - using NUnit.Framework; - - public class ReferenceTests - { - [Test] - public void ReferencePointers() - { - var diag = new AsyncApiDiagnostic(); - var versionService = new AsyncApiV2VersionService(diag); - var externalFragment = versionService.ConvertToAsyncApiReference("https://github.com/test/test#whatever", ReferenceType.None); - var internalFragment = versionService.ConvertToAsyncApiReference("#/components/servers/server1", ReferenceType.None); - var localFile = versionService.ConvertToAsyncApiReference("./local/some/folder/whatever.yaml", ReferenceType.None); - var externalFile = versionService.ConvertToAsyncApiReference("https://github.com/test/whatever.yaml", ReferenceType.None); - - externalFragment.ExternalResource.Should().Be("https://github.com/test/test"); - externalFragment.Id.Should().Be("whatever"); - externalFragment.IsFragment.Should().BeTrue(); - externalFragment.IsExternal.Should().BeTrue(); - - internalFragment.ExternalResource.Should().Be(null); - internalFragment.Id.Should().Be("#/components/servers/server1"); - internalFragment.IsFragment.Should().BeTrue(); - internalFragment.IsExternal.Should().BeFalse(); - - localFile.ExternalResource.Should().Be("./local/some/folder/whatever.yaml"); - localFile.Id.Should().Be(null); - localFile.IsFragment.Should().BeFalse(); - - externalFile.ExternalResource.Should().Be("https://github.com/test/whatever.yaml"); - externalFile.Id.Should().Be(null); - externalFile.IsFragment.Should().BeFalse(); - } - - [Test] - public void Reference() - { - var json = - """ - { - "asyncapi": "2.6.0", - "info": { }, - "servers": { - "production": { - "$ref": "https://github.com/test/test#whatever" - } - } - } - """; - - var doc = new AsyncApiStringReader().Read(json, out var diag); - var reference = doc.Servers.First().Value as AsyncApiServerReference; - reference.Reference.ExternalResource.Should().Be("https://github.com/test/test"); - reference.Reference.Id.Should().Be("whatever"); - reference.Reference.HostDocument.Should().Be(doc); - reference.Reference.IsFragment.Should().BeTrue(); - } - - [Test] - public void ResolveExternalReference() - { - var json = - """ - { - "asyncapi": "2.6.0", - "info": { }, - "servers": { - "production": { - "$ref": "https://gist.githubusercontent.com/VisualBean/7dc9607d735122483e1bb7005ff3ad0e/raw/458729e4d56636ef3bb34762f4a5731ea5043bdf/servers.json/#/servers/0" - } - } - } - """; - - var doc = new AsyncApiStringReader(new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences }).Read(json, out var diag); - var reference = doc.Servers.First().Value as AsyncApiServerReference; - //reference.Reference.Id.Should().Be("whatever"); - //reference.Reference.HostDocument.Should().Be(doc); - //reference.Reference.IsFragment.Should().BeTrue(); - } - - - [Test] - public void ServerReference_WithComponentReference_ResolvesReference() - { - var json = - """ - { - "asyncapi": "2.6.0", - "info": { }, - "servers": { - "production": { - "$ref": "#/components/servers/whatever" - } - }, - "components": { - "servers": { - "whatever": { - "url": "wss://production.gigantic-server.com:443", - "protocol": "wss", - "protocolVersion": "1.0.0", - "description": "The production API server", - "variables": { - "username": { - "default": "demo", - "description": "This value is assigned by the service provider" - }, - "password": { - "default": "demo", - "description": "This value is assigned by the service provider" - } - } - } - } - } - } - """; - - var doc = new AsyncApiStringReader().Read(json, out var diag); - var reference = doc.Servers.First().Value as AsyncApiServerReference; - reference.Reference.ExternalResource.Should().BeNull(); - reference.Reference.Id.Should().Be("whatever"); - reference.Reference.HostDocument.Should().Be(doc); - reference.Reference.IsFragment.Should().BeTrue(); - reference.Url.Should().Be("wss://production.gigantic-server.com:443"); - - } - } -} \ No newline at end of file From c2008ff0c898413b1e26141ac30d92805697f3bd Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 27 Jan 2025 14:56:22 +0100 Subject: [PATCH 07/20] r --- test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 0dd091db..3655db38 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -44,7 +44,7 @@ public class AsyncApiSchema_Should : TestBase ExternalDocs = new AsyncApiExternalDocumentation { Url = new Uri("http://example.com/externalDocs"), - }, + }, }; public static AsyncApiJsonSchema AdvancedSchemaObject = new AsyncApiJsonSchema From 53e2f124f4114b7646d37b9166cebf3032db644d Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 27 Jan 2025 14:59:45 +0100 Subject: [PATCH 08/20] reset bindings --- src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj index a774870f..2c678678 100644 --- a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj +++ b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj @@ -5,7 +5,6 @@ AsyncAPI.NET.Bindings LEGO.AsyncAPI.Bindings LEGO.AsyncAPI.Bindings - netstandard2.0;net8 From 124776b33f40f72f4f582d65b033febaf30728f4 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 27 Jan 2025 15:56:22 +0100 Subject: [PATCH 09/20] minor --- src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs | 8 ++------ .../{DefaultFileLoader.cs => DefaultStreamLoader.cs} | 0 2 files changed, 2 insertions(+), 6 deletions(-) rename src/LEGO.AsyncAPI.Readers/Services/{DefaultFileLoader.cs => DefaultStreamLoader.cs} (100%) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 068b6b42..73bdafc6 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -3,20 +3,16 @@ namespace LEGO.AsyncAPI.Readers { using System; - using System.Collections.Generic; using System.IO; using System.Linq; - using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; - using Json.More; using Json.Pointer; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.Interface; using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Services; @@ -238,7 +234,7 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream); } - var component = this.ResolveArtifactReferences(stream, reference, diagnostic); + var component = this.ResolveStreamReferences(stream, reference, diagnostic); if (component == null) { diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); @@ -268,7 +264,7 @@ private JsonNode ReadToJson(Stream stream) return default; } - private IAsyncApiSerializable ResolveArtifactReferences(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) { JsonNode json = null; try diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultFileLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs similarity index 100% rename from src/LEGO.AsyncAPI.Readers/Services/DefaultFileLoader.cs rename to src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs From f9229797b85457fdda7b70bd1772a46bcc5834a6 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 Jan 2025 09:26:47 +0100 Subject: [PATCH 10/20] message- & operation traits plus minor formatting fixes across the solution --- src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs | 30 ++++---- src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs | 24 +++--- .../Sns/PrincipalObject.cs | 2 + .../Sns/PrincipalStar.cs | 2 + src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs | 1 - src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs | 30 ++++---- src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs | 22 +++--- .../Sqs/PrincipalObject.cs | 2 + .../Sqs/PrincipalStar.cs | 2 + src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs | 1 - .../AsyncApiReaderSettings.cs | 1 - .../Interface/IStreamLoader.cs | 1 - .../AsyncApiRemoteReferenceCollector.cs | 6 +- .../V2/AsyncApiAvroSchemaDeserializer.cs | 2 - .../V2/AsyncApiComponentsDeserializer.cs | 4 +- .../V2/AsyncApiMessageTraitDeserializer.cs | 2 +- .../V2/AsyncApiOperationTraitDeserializer.cs | 2 +- src/LEGO.AsyncAPI.Readers/YamlConverter.cs | 2 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 2 - .../Models/AsyncApiComponents.cs | 12 +-- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 1 - .../Models/AsyncApiMessageTrait.cs | 54 ++++--------- .../Models/AsyncApiOperationTrait.cs | 40 +++------- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 1 - src/LEGO.AsyncAPI/Models/AsyncApiServer.cs | 1 - .../Interfaces/IAsyncApiReferenceable.cs | 2 - .../References/AsyncApiChannelReference.cs | 3 +- .../AsyncApiCorrelationIdReference.cs | 3 +- .../References/AsyncApiMessageReference.cs | 3 +- .../AsyncApiMessageTraitReference.cs | 75 +++++++++++++++++++ .../AsyncApiOperationTraitReference.cs | 61 +++++++++++++++ .../References/AsyncApiParameterReference.cs | 3 +- .../AsyncApiSecuritySchemeReference.cs | 2 + .../References/AsyncApiServerReference.cs | 1 - .../AsyncApiServerVariableReference.cs | 3 +- .../Services/AsyncApiReferenceResolver.cs | 9 +-- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 6 +- .../Validation/Rules/AsyncApiDocumentRules.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 74 +++--------------- .../AsyncApiReaderTests.cs | 3 +- .../WebSockets/WebSocketBindings_Should.cs | 2 +- .../Models/AsyncApiAnyTests.cs | 10 +-- .../Models/AsyncApiMessage_Should.cs | 2 +- .../Models/AsyncApiSchema_Should.cs | 2 +- .../Validation/ValidationRuleTests.cs | 1 - 45 files changed, 271 insertions(+), 243 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs index 38b21da9..fca3b30c 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs @@ -39,25 +39,25 @@ public static Condition Parse(ParseNode node) switch (node) { case MapNode mapNode: - { - var conditionValues = new Dictionary>(); - foreach (var conditionNode in mapNode) { - switch (conditionNode.Value) + var conditionValues = new Dictionary>(); + foreach (var conditionNode in mapNode) { - case MapNode conditionValueNode: - conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => - new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) - .ToDictionary(x => x.Key, x => x.Value))); - break; - default: - throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + - $"AWS condition values should be one or more key value pairs."); + switch (conditionNode.Value) + { + case MapNode conditionValueNode: + conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => + new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) + .ToDictionary(x => x.Key, x => x.Value))); + break; + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"AWS condition values should be one or more key value pairs."); + } } - } - return new Condition(conditionValues); - } + return new Condition(conditionValues); + } default: throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs index a803f6c2..2d630641 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Bindings.Sns; using System; @@ -28,20 +30,20 @@ public static Principal Parse(ParseNode node) return new PrincipalStar(); case MapNode mapNode: - { - var propertyNode = mapNode.First(); - if (!IsValidPrincipalProperty(propertyNode.Name)) { - throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + - $"Node should contain a valid AWS principal property name."); - } + var propertyNode = mapNode.First(); + if (!IsValidPrincipalProperty(propertyNode.Name)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a valid AWS principal property name."); + } - var principalValue = new KeyValuePair( - propertyNode.Name, - StringOrStringList.Parse(propertyNode.Value)); + var principalValue = new KeyValuePair( + propertyNode.Name, + StringOrStringList.Parse(propertyNode.Value)); - return new PrincipalObject(principalValue); - } + return new PrincipalObject(principalValue); + } default: throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs index 209be8bf..946fa4d6 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Bindings.Sns; using System; diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs index c885d252..bd0e37bf 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Bindings.Sns; using System; diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs index da93cfbf..5a7a248c 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Bindings.Sns using System; using System.Collections.Generic; using LEGO.AsyncAPI.Attributes; - using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs index 93bdf733..9172f3e3 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs @@ -39,25 +39,25 @@ public static Condition Parse(ParseNode node) switch (node) { case MapNode mapNode: - { - var conditionValues = new Dictionary>(); - foreach (var conditionNode in mapNode) { - switch (conditionNode.Value) + var conditionValues = new Dictionary>(); + foreach (var conditionNode in mapNode) { - case MapNode conditionValueNode: - conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => - new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) - .ToDictionary(x => x.Key, x => x.Value))); - break; - default: - throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + - $"AWS condition values should be one or more key value pairs."); + switch (conditionNode.Value) + { + case MapNode conditionValueNode: + conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => + new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) + .ToDictionary(x => x.Key, x => x.Value))); + break; + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"AWS condition values should be one or more key value pairs."); + } } - } - return new Condition(conditionValues); - } + return new Condition(conditionValues); + } default: throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs index 2821f952..eee2b4fe 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs @@ -30,20 +30,20 @@ public static Principal Parse(ParseNode node) return new PrincipalStar(); case MapNode mapNode: - { - var propertyNode = mapNode.First(); - if (!IsValidPrincipalProperty(propertyNode.Name)) { - throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + - $"Node should contain a valid AWS principal property name."); - } + var propertyNode = mapNode.First(); + if (!IsValidPrincipalProperty(propertyNode.Name)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a valid AWS principal property name."); + } - var principalValue = new KeyValuePair( - propertyNode.Name, - StringOrStringList.Parse(propertyNode.Value)); + var principalValue = new KeyValuePair( + propertyNode.Name, + StringOrStringList.Parse(propertyNode.Value)); - return new PrincipalObject(principalValue); - } + return new PrincipalObject(principalValue); + } default: throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs index 61e6e546..a1613247 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Bindings.Sqs; using System; diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs index 1705b966..a49b02c6 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Bindings.Sqs; using System; diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs index 4a9c5303..0c2873d2 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs @@ -5,7 +5,6 @@ namespace LEGO.AsyncAPI.Bindings.Sqs using System; using System.Collections.Generic; using LEGO.AsyncAPI.Attributes; - using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index 523e1484..890b83e6 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -8,7 +8,6 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; - using LEGO.AsyncAPI.Readers.Services; using LEGO.AsyncAPI.Validations; public enum ReferenceResolutionSetting diff --git a/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs index 14da05c9..d0daca7c 100644 --- a/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Interface/IStreamLoader.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Readers.Interface { using System; using System.IO; - using System.Text.Json.Nodes; using System.Threading.Tasks; public interface IStreamLoader diff --git a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs index 48c710e1..1d33251e 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -14,9 +14,9 @@ internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase public AsyncApiRemoteReferenceCollector( AsyncApiDocument currentDocument) - { - this.currentDocument = currentDocument; - } + { + this.currentDocument = currentDocument; + } /// /// List of all external references collected from AsyncApiDocument. /// diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index baae8873..1ac2c611 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -2,8 +2,6 @@ namespace LEGO.AsyncAPI.Readers { - using System; - using System.Threading; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Avro.LogicalTypes; diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index 53124676..7cdba6dd 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -17,8 +17,8 @@ internal static partial class AsyncApiV2Deserializer { "securitySchemes", (a, n) => a.SecuritySchemes = n.CreateMap(LoadSecurityScheme) }, { "parameters", (a, n) => a.Parameters = n.CreateMap(LoadParameter) }, { "correlationIds", (a, n) => a.CorrelationIds = n.CreateMap(LoadCorrelationId) }, - { "operationTraits", (a, n) => a.OperationTraits = n.CreateMapWithReference(ReferenceType.OperationTrait, LoadOperationTrait) }, - { "messageTraits", (a, n) => a.MessageTraits = n.CreateMapWithReference(ReferenceType.MessageTrait, LoadMessageTrait) }, + { "operationTraits", (a, n) => a.OperationTraits = n.CreateMap(LoadOperationTrait) }, + { "messageTraits", (a, n) => a.MessageTraits = n.CreateMap(LoadMessageTrait) }, { "serverBindings", (a, n) => a.ServerBindings = n.CreateMapWithReference(ReferenceType.ServerBindings, LoadServerBindings) }, { "channelBindings", (a, n) => a.ChannelBindings = n.CreateMapWithReference(ReferenceType.ChannelBindings, LoadChannelBindings) }, { "operationBindings", (a, n) => a.OperationBindings = n.CreateMapWithReference(ReferenceType.OperationBindings, LoadOperationBindings) }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs index 0d229e92..40e8a945 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs @@ -38,7 +38,7 @@ public static AsyncApiMessageTrait LoadMessageTrait(ParseNode node) if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.MessageTrait, pointer); + return new AsyncApiMessageTraitReference(pointer); } var messageTrait = new AsyncApiMessageTrait(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationTraitDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationTraitDeserializer.cs index 561456e9..ee503d91 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationTraitDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationTraitDeserializer.cs @@ -31,7 +31,7 @@ public static AsyncApiOperationTrait LoadOperationTrait(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.OperationTrait, pointer); + return new AsyncApiOperationTraitReference(pointer); } var operationTrait = new AsyncApiOperationTrait(); diff --git a/src/LEGO.AsyncAPI.Readers/YamlConverter.cs b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs index 29225f06..942bc28f 100644 --- a/src/LEGO.AsyncAPI.Readers/YamlConverter.cs +++ b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs @@ -58,7 +58,7 @@ private static JsonValue ToJsonValue(this YamlScalarNode yaml, CultureInfo cultu ? JsonValue.Create(d) : bool.TryParse(yaml.Value, out var b) ? JsonValue.Create(b) - : JsonValue.Create(yaml.Value) !; + : JsonValue.Create(yaml.Value)!; case ScalarStyle.SingleQuoted: case ScalarStyle.DoubleQuoted: case ScalarStyle.Literal: diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index 310ddce6..ea6572e6 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -182,8 +182,6 @@ public T ResolveReference(string location) return default; } - - private Uri ToLocationUrl(string location) { return new(location, UriKind.RelativeOrAbsolute); diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index 9ac21433..dee0bbb0 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -260,11 +260,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.OperationTraits, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.OperationTrait && - component.Reference.FragmentId == key) + if (component is AsyncApiOperationTraitReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -278,11 +276,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.MessageTraits, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.MessageTrait && - component.Reference.FragmentId == key) + if (component is AsyncApiMessageTraitReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 1b66525f..89edf733 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -6,7 +6,6 @@ namespace LEGO.AsyncAPI.Models using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - using LEGO.AsyncAPI.Services; /// /// This is the root document object for the API specification. It combines resource listing and API declaration together into one document. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs index 2d972363..1e62adc0 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs @@ -10,22 +10,22 @@ namespace LEGO.AsyncAPI.Models /// /// Describes a trait that MAY be applied to a Message Object. /// - public class AsyncApiMessageTrait : IAsyncApiExtensible, IAsyncApiReferenceable, IAsyncApiSerializable + public class AsyncApiMessageTrait : IAsyncApiExtensible, IAsyncApiSerializable { /// /// Unique string used to identify the message. The id MUST be unique among all messages described in the API. /// - public string MessageId { get; set; } + public virtual string MessageId { get; set; } /// /// schema definition of the application headers. Schema MUST be of type "object". /// - public AsyncApiJsonSchema Headers { get; set; } + public virtual AsyncApiJsonSchema Headers { get; set; } /// /// definition of the correlation ID used for message tracing or matching. /// - public AsyncApiCorrelationId CorrelationId { get; set; } + public virtual AsyncApiCorrelationId CorrelationId { get; set; } /// /// a string containing the name of the schema format used to define the message payload. @@ -33,78 +33,56 @@ public class AsyncApiMessageTrait : IAsyncApiExtensible, IAsyncApiReferenceable, /// /// If omitted, implementations should parse the payload as a Schema object. /// - public string SchemaFormat { get; set; } + public virtual string SchemaFormat { get; set; } /// /// the content type to use when encoding/decoding a message's payload. /// - public string ContentType { get; set; } + public virtual string ContentType { get; set; } /// /// a machine-friendly name for the message. /// - public string Name { get; set; } + public virtual string Name { get; set; } /// /// a human-friendly title for the message. /// - public string Title { get; set; } + public virtual string Title { get; set; } /// /// a short summary of what the message is about. /// - public string Summary { get; set; } + public virtual string Summary { get; set; } /// /// a verbose explanation of the message. CommonMark syntax can be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// a list of tags for API documentation control. Tags can be used for logical grouping of messages. /// - public IList Tags { get; set; } = new List(); + public virtual IList Tags { get; set; } = new List(); /// /// additional external documentation for this message. /// - public AsyncApiExternalDocumentation ExternalDocs { get; set; } + public virtual AsyncApiExternalDocumentation ExternalDocs { get; set; } /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the message. /// - public AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); + public virtual AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); /// /// list of examples. /// - public IList Examples { get; set; } = new List(); + public virtual IList Examples { get; set; } = new List(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public bool UnresolvedReference { get; set; } - - /// - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiOperationTrait.cs b/src/LEGO.AsyncAPI/Models/AsyncApiOperationTrait.cs index c7e0d10e..9260808e 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiOperationTrait.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiOperationTrait.cs @@ -10,7 +10,7 @@ namespace LEGO.AsyncAPI.Models /// /// Describes a trait that MAY be applied to an Operation Object. /// - public class AsyncApiOperationTrait : IAsyncApiExtensible, IAsyncApiReferenceable, IAsyncApiSerializable + public class AsyncApiOperationTrait : IAsyncApiExtensible, IAsyncApiSerializable { /// /// unique string used to identify the operation. @@ -18,59 +18,37 @@ public class AsyncApiOperationTrait : IAsyncApiExtensible, IAsyncApiReferenceabl /// /// The id MUST be unique among all operations described in the API. /// - public string OperationId { get; set; } + public virtual string OperationId { get; set; } /// /// a short summary of what the operation is about. /// - public string Summary { get; set; } + public virtual string Summary { get; set; } /// /// a short summary of what the operation is about. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// a list of tags for API documentation control. Tags can be used for logical grouping of operations. /// - public IList Tags { get; set; } = new List(); + public virtual IList Tags { get; set; } = new List(); /// /// additional external documentation for this operation. /// - public AsyncApiExternalDocumentation ExternalDocs { get; set; } + public virtual AsyncApiExternalDocumentation ExternalDocs { get; set; } /// /// a map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the operation. /// - public AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); + public virtual AsyncApiBindings Bindings { get; set; } = new AsyncApiBindings(); /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public bool UnresolvedReference { get; set; } - - /// - public AsyncApiReference Reference { get; set; } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 3e5fc986..c0292bb2 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Models { using System; - using System.Linq; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs index d977d83e..396b65d9 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs index 54e344e4..b1de15ae 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs @@ -2,8 +2,6 @@ namespace LEGO.AsyncAPI.Models.Interfaces { - using LEGO.AsyncAPI.Writers; - public interface IAsyncApiReferenceable : IAsyncApiSerializable { /// diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs index 56d393fe..dd4aa4e8 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs @@ -1,6 +1,7 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs index f3edde84..5bc56e2b 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs @@ -1,6 +1,7 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs index 944e867d..09bc4fe2 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -1,6 +1,7 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs new file mode 100644 index 00000000..9beefe3b --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs @@ -0,0 +1,75 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of a message trait this application MAY use. + /// + public class AsyncApiMessageTraitReference : AsyncApiMessageTrait, IAsyncApiReferenceable + { + private AsyncApiMessageTrait target; + + private AsyncApiMessageTrait Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiMessageTraitReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.MessageTrait); + } + + public override string MessageId { get => this.Target?.MessageId; set => this.Target.MessageId = value; } + + public override AsyncApiJsonSchema Headers { get => this.Target?.Headers; set => this.Target.Headers = value; } + + public override AsyncApiCorrelationId CorrelationId { get => this.Target?.CorrelationId; set => this.Target.CorrelationId = value; } + + public override string SchemaFormat { get => this.Target?.SchemaFormat; set => this.Target.SchemaFormat = value; } + + public override string ContentType { get => this.Target?.ContentType; set => this.Target.ContentType = value; } + + public override string Name { get => this.Target?.Name; set => this.Target.Name = value; } + + public override string Title { get => this.Target?.Title; set => this.Target.Title = value; } + + public override string Summary { get => this.Target?.Summary; set => this.Target.Summary = value; } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override IList Tags { get => this.Target?.Tags; set => this.Target.Tags = value; } + + public override AsyncApiExternalDocumentation ExternalDocs { get => this.Target?.ExternalDocs; set => this.Target.ExternalDocs = value; } + + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } + + public override IList Examples { get => this.Target?.Examples; set => this.Target.Examples = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs new file mode 100644 index 00000000..6968cc0c --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs @@ -0,0 +1,61 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// The definition of an operation trait this application MAY use. + /// + public class AsyncApiOperationTraitReference : AsyncApiOperationTrait, IAsyncApiReferenceable + { + private AsyncApiOperationTrait target; + + private AsyncApiOperationTrait Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiOperationTraitReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.OperationTrait); + } + + public override string OperationId { get => this.Target?.OperationId; set => this.Target.OperationId = value; } + + public override string Summary { get => this.Target?.Summary; set => this.Target.Summary = value; } + + public override string Description { get => this.Target?.Description; set => this.Target.Description = value; } + + public override IList Tags { get => this.Target?.Tags; set => this.Target.Tags = value; } + + public override AsyncApiExternalDocumentation ExternalDocs { get => this.Target?.ExternalDocs; set => this.Target.ExternalDocs = value; } + + public override AsyncApiBindings Bindings { get => this.Target?.Bindings; set => this.Target.Bindings = value; } + + public override IDictionary Extensions { get => this.Target?.Extensions; set => this.Target.Extensions = value; } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + else + { + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs index d0a9a677..65d23d0d 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs @@ -1,6 +1,7 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs index c8cc559f..a38d95df 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -1,3 +1,5 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { using System; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs index 46e81e9f..66f163f6 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs index 6e9041ac..a6fbc3cf 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs @@ -1,6 +1,7 @@ +// Copyright (c) The LEGO Group. All rights reserved. + namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 728eb5db..2def4f53 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -1,15 +1,8 @@ // Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Services { - using System; - using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Exceptions; - using LEGO.AsyncAPI.Models; - using LEGO.AsyncAPI.Models.Interfaces; - /// /// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects. /// - + } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index b9f04140..7e33d3d7 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -513,8 +513,9 @@ private void Walk(IList traits) internal void Walk(AsyncApiOperationTrait trait, bool isComponent = false) { - if (trait == null || this.ProcessAsReference(trait, isComponent)) + if (trait is AsyncApiOperationTraitReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -585,8 +586,9 @@ private void Walk(IList traits) internal void Walk(AsyncApiMessageTrait trait, bool isComponent = false) { - if (trait == null || this.ProcessAsReference(trait, isComponent)) + if (trait is AsyncApiMessageTraitReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs index d866c4df..e82f3c7c 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs @@ -67,7 +67,7 @@ public static class AsyncApiDocumentRules finally { context.Exit(); - } + } }); private static string GetKeySignature(string path) diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index e5695743..7b9315af 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -300,14 +300,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "receiveLightMeasurement", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - FragmentId = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { @@ -330,18 +323,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "turnOn", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - FragmentId = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessageReference("#/components/messages/turnOnOff") + new AsyncApiMessageReference("#/components/messages/turnOnOff"), }, }, }) @@ -360,25 +346,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "turnOff", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - FragmentId = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessageReference("#/components/messages/turnOnOff") - { - Reference = new AsyncApiReference() - { - FragmentId = "turnOnOff", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/turnOnOff"), }, }, }) @@ -397,14 +369,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "dimLight", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - FragmentId = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { @@ -420,14 +385,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() ContentType = "application/json", Traits = new List() { - new AsyncApiMessageTrait() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.MessageTrait, - FragmentId = "commonHeaders", - }, - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, Payload = new AsyncApiJsonSchemaPayload { @@ -445,14 +403,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Summary = "Command a particular streetlight to turn the lights on or off.", Traits = new List() { - new AsyncApiMessageTrait() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.MessageTrait, - FragmentId = "commonHeaders", - }, - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, Payload = new AsyncApiJsonSchemaPayload() { @@ -470,14 +421,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() Summary = "Command a particular streetlight to dim the lights.", Traits = new List() { - new AsyncApiMessageTrait() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.MessageTrait, - FragmentId = "commonHeaders", - }, - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, Payload = new AsyncApiJsonSchemaPayload() { diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index ebb7dfc3..a229b861 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -5,7 +5,6 @@ namespace LEGO.AsyncAPI.Tests using System; using System.Collections.Generic; using System.Linq; - using System.Text.Json.Nodes; using FluentAssertions; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; @@ -432,7 +431,7 @@ public void Read_WithBasicPlusSecuritySchemeDeserializes() [Test] public void Read_WithWrongReference_AddsError() { - var yaml = + var yaml = """ asyncapi: 2.3.0 info: diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs index 51815a25..203177ac 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs @@ -8,7 +8,7 @@ namespace LEGO.AsyncAPI.Tests.Bindings.WebSockets using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; using NUnit.Framework; - + public class WebSocketBindings_Should : TestBase { [Test] diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs index 1dfdc59a..c0b233fc 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs @@ -1,12 +1,12 @@ // Copyright (c) The LEGO Group. All rights reserved. -using System.Collections.Generic; -using System.Linq; -using LEGO.AsyncAPI.Models; -using NUnit.Framework; - namespace LEGO.AsyncAPI.Tests { + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models; + using NUnit.Framework; + public class AsyncApiAnyTests { [Test] diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index 6cc21365..f300f97d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -126,7 +126,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() actual.Should() .BePlatformAgnosticEquivalentTo(expected); message.Should().BeEquivalentTo(deserializedMessage); - } + } [Test] public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 60b18811..945e6753 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -44,7 +44,7 @@ public class AsyncApiSchema_Should : TestBase ExternalDocs = new AsyncApiExternalDocumentation { Url = new Uri("http://example.com/externalDocs"), - }, + }, }; public static AsyncApiJsonSchema AdvancedSchemaObject = new AsyncApiJsonSchema diff --git a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs index 5d9f7bec..2b1d3ebf 100644 --- a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests.Validation { using FluentAssertions; using LEGO.AsyncAPI.Readers; - using LEGO.AsyncAPI.Validations; using NUnit.Framework; using System.Linq; From 4a81385eee40d8fc6e9ac7fff5cc6ba1d0e8d4e2 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 Jan 2025 12:02:12 +0100 Subject: [PATCH 11/20] partiall done with json schema --- .../V2/AsyncApiComponentsDeserializer.cs | 2 +- .../V2/AsyncApiSchemaDeserializer.cs | 6 +- .../Models/AsyncApiComponents.cs | 14 +- .../Models/AsyncApiJsonSchemaPayload.cs | 236 ++++++------- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 2 +- .../Models/Avro/AvroPrimitive.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 6 +- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 2 +- .../Models/Avro/LogicalTypes/AvroDuration.cs | 2 +- .../Avro/LogicalTypes/AvroLogicalType.cs | 6 +- .../Models/Interfaces/IAsyncApiPayload.cs | 2 +- .../Models/JsonSchema/AsyncApiJsonSchema.cs | 146 +++----- .../AsyncApiJsonSchemaPayloadReference.cs | 315 +++++++++++++++++ .../References/AsyncApiJsonSchemaReference.cs | 318 ++++++++++++++++++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 3 +- .../AsyncApiDocumentV2Tests.cs | 18 +- 20 files changed, 812 insertions(+), 276 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index 7cdba6dd..1e1b44f9 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -10,7 +10,7 @@ internal static partial class AsyncApiV2Deserializer { private static FixedFieldMap componentsFixedFields = new() { - { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, AsyncApiSchemaDeserializer.LoadSchema) }, + { "schemas", (a, n) => a.Schemas = n.CreateMap(AsyncApiSchemaDeserializer.LoadSchema) }, { "servers", (a, n) => a.Servers = n.CreateMap(LoadServer) }, { "channels", (a, n) => a.Channels = n.CreateMap(LoadChannel) }, { "messages", (a, n) => a.Messages = n.CreateMap(LoadMessage) }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs index ba3130a0..45b74674 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs @@ -229,11 +229,7 @@ public static AsyncApiJsonSchema LoadSchema(ParseNode node) if (pointer != null) { - return new AsyncApiJsonSchema - { - UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToAsyncApiReference(pointer, ReferenceType.Schema), - }; + return new AsyncApiJsonSchemaReference(pointer); } var schema = new AsyncApiJsonSchema(); diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index dee0bbb0..bd9e5aef 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -101,17 +101,17 @@ public void SerializeV2(IAsyncApiWriter writer) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); - if (loops.TryGetValue(typeof(AsyncApiJsonSchema), out List schemas)) + if (loops.TryGetValue(typeof(AsyncApiJsonSchemaReference), out List schemas)) { - var asyncApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.FragmentId); + var asyncApiSchemas = schemas.Cast().Distinct().ToList() + .ToDictionary(k => k.Reference.FragmentId); writer.WriteOptionalMap( AsyncApiConstants.Schemas, this.Schemas, (w, key, component) => { - component.SerializeV2WithoutReference(w); + component.SerializeV2(w); }); } @@ -130,11 +130,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.Schemas, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Schema && - component.Reference.FragmentId == key) + if (component is AsyncApiJsonSchemaReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs index 91c6f68f..b13e258b 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs @@ -1,127 +1,109 @@ -// Copyright (c) The LEGO Group. All rights reserved. - -namespace LEGO.AsyncAPI.Models -{ - using System.Collections.Generic; - using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; - - public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload - { - private readonly AsyncApiJsonSchema schema; - - public AsyncApiJsonSchemaPayload() - { - this.schema = new AsyncApiJsonSchema(); - } - - public AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) - { - this.schema = schema; - } - - public string Title { get => this.schema.Title; set => this.schema.Title = value; } - - public SchemaType? Type { get => this.schema.Type; set => this.schema.Type = value; } - - public string Format { get => this.schema.Format; set => this.schema.Format = value; } - - public string Description { get => this.schema.Description; set => this.schema.Description = value; } - - public double? Maximum { get => this.schema.Maximum; set => this.schema.Maximum = value; } - - public double? ExclusiveMaximum { get => this.schema.ExclusiveMaximum; set => this.schema.ExclusiveMaximum = value; } - - public double? Minimum { get => this.schema.Minimum; set => this.schema.Minimum = value; } - - public double? ExclusiveMinimum { get => this.schema.ExclusiveMinimum; set => this.schema.ExclusiveMinimum = value; } - - public int? MaxLength { get => this.schema.MaxLength; set => this.schema.MaxLength = value; } - - public int? MinLength { get => this.schema.MinLength; set => this.schema.MinLength = value; } - - public string Pattern { get => this.schema.Pattern; set => this.schema.Pattern = value; } - - public double? MultipleOf { get => this.schema.MultipleOf; set => this.schema.MultipleOf = value; } - - public AsyncApiAny Default { get => this.schema.Default; set => this.schema.Default = value; } - - public bool ReadOnly { get => this.schema.ReadOnly; set => this.schema.ReadOnly = value; } - - public bool WriteOnly { get => this.schema.WriteOnly; set => this.schema.WriteOnly = value; } - - public IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; } - - public IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; } - - public IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; } - - public AsyncApiJsonSchema Not { get => this.schema.Not; set => this.schema.Not = value; } - - public AsyncApiJsonSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } - - public AsyncApiJsonSchema If { get => this.schema.If; set => this.schema.If = value; } - - public AsyncApiJsonSchema Then { get => this.schema.Then; set => this.schema.Then = value; } - - public AsyncApiJsonSchema Else { get => this.schema.Else; set => this.schema.Else = value; } - - public ISet Required { get => this.schema.Required; set => this.schema.Required = value; } - - public AsyncApiJsonSchema Items { get => this.schema.Items; set => this.schema.Items = value; } - - public AsyncApiJsonSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; } - - public int? MaxItems { get => this.schema.MaxItems; set => this.schema.MaxItems = value; } - - public int? MinItems { get => this.schema.MinItems; set => this.schema.MinItems = value; } - - public bool? UniqueItems { get => this.schema.UniqueItems; set => this.schema.UniqueItems = value; } - - public IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; } - - public int? MaxProperties { get => this.schema.MaxProperties; set => this.schema.MaxProperties = value; } - - public int? MinProperties { get => this.schema.MinProperties; set => this.schema.MinProperties = value; } - - public IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; } - - public AsyncApiJsonSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; } - - public string Discriminator { get => this.schema.Discriminator; set => this.schema.Discriminator = value; } - - public IList Enum { get => this.schema.Enum; set => this.schema.Enum = value; } - - public IList Examples { get => this.schema.Examples; set => this.schema.Examples = value; } - - public AsyncApiAny Const { get => this.schema.Const; set => this.schema.Const = value; } - - public bool Nullable { get => this.schema.Nullable; set => this.schema.Nullable = value; } - - public AsyncApiExternalDocumentation ExternalDocs { get => this.schema.ExternalDocs; set => this.schema.ExternalDocs = value; } - - public bool Deprecated { get => this.schema.Deprecated; set => this.schema.Deprecated = value; } - - public bool UnresolvedReference { get => this.schema.UnresolvedReference; set => this.schema.UnresolvedReference = value; } - - public AsyncApiReference Reference { get => this.schema.Reference; set => this.schema.Reference = value; } - - public IDictionary Extensions { get => this.schema.Extensions; set => this.schema.Extensions = value; } - - public AsyncApiJsonSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } - - public static implicit operator AsyncApiJsonSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; - - public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) => new AsyncApiJsonSchemaPayload(schema); - - public void SerializeV2(IAsyncApiWriter writer) - { - this.schema.SerializeV2(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) - { - this.schema.SerializeV2WithoutReference(writer); - } - } -} +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload { private readonly AsyncApiJsonSchema schema; + + public AsyncApiJsonSchemaPayload() + { + this.schema = new AsyncApiJsonSchema(); + } + + public AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) + { + this.schema = schema; + } + + public virtual string Title { get => this.schema.Title; set => this.schema.Title = value; } + + public virtual SchemaType? Type { get => this.schema.Type; set => this.schema.Type = value; } + + public virtual string Format { get => this.schema.Format; set => this.schema.Format = value; } + + public virtual string Description { get => this.schema.Description; set => this.schema.Description = value; } + + public virtual double? Maximum { get => this.schema.Maximum; set => this.schema.Maximum = value; } + + public virtual double? ExclusiveMaximum { get => this.schema.ExclusiveMaximum; set => this.schema.ExclusiveMaximum = value; } + + public virtual double? Minimum { get => this.schema.Minimum; set => this.schema.Minimum = value; } + + public virtual double? ExclusiveMinimum { get => this.schema.ExclusiveMinimum; set => this.schema.ExclusiveMinimum = value; } + + public virtual int? MaxLength { get => this.schema.MaxLength; set => this.schema.MaxLength = value; } + + public virtual int? MinLength { get => this.schema.MinLength; set => this.schema.MinLength = value; } + + public virtual string Pattern { get => this.schema.Pattern; set => this.schema.Pattern = value; } + + public virtual double? MultipleOf { get => this.schema.MultipleOf; set => this.schema.MultipleOf = value; } + + public virtual AsyncApiAny Default { get => this.schema.Default; set => this.schema.Default = value; } + + public virtual bool ReadOnly { get => this.schema.ReadOnly; set => this.schema.ReadOnly = value; } + + public virtual bool WriteOnly { get => this.schema.WriteOnly; set => this.schema.WriteOnly = value; } + + public virtual IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; } + + public virtual IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; } + + public virtual IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; } + + public virtual AsyncApiJsonSchema Not { get => this.schema.Not; set => this.schema.Not = value; } + + public virtual AsyncApiJsonSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } + + public virtual AsyncApiJsonSchema If { get => this.schema.If; set => this.schema.If = value; } + + public virtual AsyncApiJsonSchema Then { get => this.schema.Then; set => this.schema.Then = value; } + + public virtual AsyncApiJsonSchema Else { get => this.schema.Else; set => this.schema.Else = value; } + + public virtual ISet Required { get => this.schema.Required; set => this.schema.Required = value; } + + public virtual AsyncApiJsonSchema Items { get => this.schema.Items; set => this.schema.Items = value; } + + public virtual AsyncApiJsonSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; } + + public virtual int? MaxItems { get => this.schema.MaxItems; set => this.schema.MaxItems = value; } + + public virtual int? MinItems { get => this.schema.MinItems; set => this.schema.MinItems = value; } + + public virtual bool? UniqueItems { get => this.schema.UniqueItems; set => this.schema.UniqueItems = value; } + + public virtual IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; } + + public virtual int? MaxProperties { get => this.schema.MaxProperties; set => this.schema.MaxProperties = value; } + + public virtual int? MinProperties { get => this.schema.MinProperties; set => this.schema.MinProperties = value; } + + public virtual IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; } + + public virtual AsyncApiJsonSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; } + + public virtual string Discriminator { get => this.schema.Discriminator; set => this.schema.Discriminator = value; } + + public virtual IList Enum { get => this.schema.Enum; set => this.schema.Enum = value; } + + public virtual IList Examples { get => this.schema.Examples; set => this.schema.Examples = value; } + + public virtual AsyncApiAny Const { get => this.schema.Const; set => this.schema.Const = value; } + + public virtual bool Nullable { get => this.schema.Nullable; set => this.schema.Nullable = value; } + + public virtual AsyncApiExternalDocumentation ExternalDocs { get => this.schema.ExternalDocs; set => this.schema.ExternalDocs = value; } + + public virtual bool Deprecated { get => this.schema.Deprecated; set => this.schema.Deprecated = value; } + + public virtual IDictionary Extensions { get => this.schema.Extensions; set => this.schema.Extensions = value; } + + public virtual AsyncApiJsonSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } + + public static implicit operator AsyncApiJsonSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; + + public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) => new AsyncApiJsonSchemaPayload(schema); + + public virtual void SerializeV2(IAsyncApiWriter writer) + { + this.schema.SerializeV2(writer); + } + } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 0958fa3d..8aa28144 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -20,7 +20,7 @@ public class AvroArray : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index dbfbb627..e7428c21 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -45,7 +45,7 @@ public class AvroEnum : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 241e8c1e..36da6f0c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -36,7 +36,7 @@ public class AvroFixed : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 8b3028c6..934d4dec 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -17,7 +17,7 @@ public class AvroMap : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 4d12c792..1f8006ff 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -20,7 +20,7 @@ public AvroPrimitive(AvroPrimitiveType type) this.Type = type.GetDisplayName(); } - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteValue(this.Type); if (this.Metadata.Any()) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index 04642f5c..a5ea1cee 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -40,7 +40,7 @@ public class AvroRecord : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index fce1df2d..081995eb 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -25,7 +25,7 @@ public static implicit operator AvroSchema(AvroPrimitiveType type) return new AvroPrimitive(type); } - public void SerializeV2(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { @@ -38,9 +38,9 @@ public void SerializeV2(IAsyncApiWriter writer) return; } - this.SerializeV2WithoutReference(writer); + this.SerializeV2Core(writer); } - public abstract void SerializeV2WithoutReference(IAsyncApiWriter writer); + public abstract void SerializeV2Core(IAsyncApiWriter writer); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs index 5762935d..19ad3a3d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -20,7 +20,7 @@ public class AvroUnion : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartArray(); foreach (var type in this.Types) diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs index 01469d0b..35b457b0 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs @@ -11,7 +11,7 @@ public class AvroDuration : AvroFixed public new int Size { get; } = 12; - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs index 235899a1..bf834ebd 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs @@ -14,11 +14,7 @@ protected AvroLogicalType(AvroPrimitiveType type) public abstract LogicalType LogicalType { get; } - public virtual void SerializeV2Core(IAsyncApiWriter writer) - { - } - - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2Core(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs index 76112af9..1da08177 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs @@ -2,7 +2,7 @@ namespace LEGO.AsyncAPI.Models.Interfaces { - public interface IAsyncApiMessagePayload : IAsyncApiSerializable, IAsyncApiReferenceable + public interface IAsyncApiMessagePayload : IAsyncApiSerializable { } } diff --git a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs index 40dbf4cc..d316c906 100644 --- a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs +++ b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs @@ -11,69 +11,69 @@ namespace LEGO.AsyncAPI.Models /// /// The Schema Object allows the definition of input and output data types. /// - public class AsyncApiJsonSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyncApiSerializable + public class AsyncApiJsonSchema : IAsyncApiExtensible, IAsyncApiSerializable { /// /// follow JSON Schema definition. Short text providing information about the data. /// - public string Title { get; set; } + public virtual string Title { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public SchemaType? Type { get; set; } + public virtual SchemaType? Type { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public string Format { get; set; } + public virtual string Format { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public double? Maximum { get; set; } + public virtual double? Maximum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public double? ExclusiveMaximum { get; set; } + public virtual double? ExclusiveMaximum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public double? Minimum { get; set; } + public virtual double? Minimum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public double? ExclusiveMinimum { get; set; } + public virtual double? ExclusiveMinimum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MaxLength { get; set; } + public virtual int? MaxLength { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MinLength { get; set; } + public virtual int? MinLength { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. /// - public string Pattern { get; set; } + public virtual string Pattern { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public double? MultipleOf { get; set; } + public virtual double? MultipleOf { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -81,7 +81,7 @@ public class AsyncApiJsonSchema : IAsyncApiReferenceable, IAsyncApiExtensible, I /// Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. /// For example, if type is string, then default can be "foo" but cannot be 1. /// - public AsyncApiAny Default { get; set; } + public virtual AsyncApiAny Default { get; set; } /// /// a value indicating whether relevant only for Schema "properties" definitions. Declares the property as "read only". @@ -91,7 +91,7 @@ public class AsyncApiJsonSchema : IAsyncApiReferenceable, IAsyncApiExtensible, I /// A property MUST NOT be marked as both readOnly and writeOnly being true. /// Default value is false. /// - public bool ReadOnly { get; set; } + public virtual bool ReadOnly { get; set; } /// /// a value indicating whether relevant only for Schema "properties" definitions. Declares the property as "write only". @@ -101,162 +101,156 @@ public class AsyncApiJsonSchema : IAsyncApiReferenceable, IAsyncApiExtensible, I /// A property MUST NOT be marked as both readOnly and writeOnly being true. /// Default value is false. /// - public bool WriteOnly { get; set; } + public virtual bool WriteOnly { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AllOf { get; set; } = new List(); + public virtual IList AllOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList OneOf { get; set; } = new List(); + public virtual IList OneOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AnyOf { get; set; } = new List(); + public virtual IList AnyOf { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public AsyncApiJsonSchema Not { get; set; } + public virtual AsyncApiJsonSchema Not { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiJsonSchema Contains { get; set; } + public virtual AsyncApiJsonSchema Contains { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiJsonSchema If { get; set; } + public virtual AsyncApiJsonSchema If { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiJsonSchema Then { get; set; } + public virtual AsyncApiJsonSchema Then { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiJsonSchema Else { get; set; } + public virtual AsyncApiJsonSchema Else { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public ISet Required { get; set; } = new HashSet(); + public virtual ISet Required { get; set; } = new HashSet(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object /// and not a standard JSON Schema. items MUST be present if the type is array. /// - public AsyncApiJsonSchema Items { get; set; } + public virtual AsyncApiJsonSchema Items { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object /// and not a standard JSON Schema. items MUST be present if the type is array. /// - public AsyncApiJsonSchema AdditionalItems { get; set; } + public virtual AsyncApiJsonSchema AdditionalItems { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MaxItems { get; set; } + public virtual int? MaxItems { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MinItems { get; set; } + public virtual int? MinItems { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public bool? UniqueItems { get; set; } + public virtual bool? UniqueItems { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced). /// - public IDictionary Properties { get; set; } = new Dictionary(); + public virtual IDictionary Properties { get; set; } = new Dictionary(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MaxProperties { get; set; } + public virtual int? MaxProperties { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public int? MinProperties { get; set; } + public virtual int? MinProperties { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html /// Value can be boolean or object. Inline or referenced schema /// MUST be of a Schema Object and not a standard JSON Schema. /// - public AsyncApiJsonSchema AdditionalProperties { get; set; } + public virtual AsyncApiJsonSchema AdditionalProperties { get; set; } - public IDictionary PatternProperties { get; set; } = new Dictionary(); + public virtual IDictionary PatternProperties { get; set; } = new Dictionary(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiJsonSchema PropertyNames { get; set; } + public virtual AsyncApiJsonSchema PropertyNames { get; set; } /// /// adds support for polymorphism. /// The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. /// - public string Discriminator { get; set; } + public virtual string Discriminator { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public IList Enum { get; set; } = new List(); + public virtual IList Enum { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public IList Examples { get; set; } = new List(); + public virtual IList Examples { get; set; } = new List(); /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public AsyncApiAny Const { get; set; } + public virtual AsyncApiAny Const { get; set; } /// /// a value indicating whether allows sending a null value for the defined schema. Default value is false. /// - public bool Nullable { get; set; } + public virtual bool Nullable { get; set; } /// /// additional external documentation for this schema. /// - public AsyncApiExternalDocumentation ExternalDocs { get; set; } + public virtual AsyncApiExternalDocumentation ExternalDocs { get; set; } /// /// a value indicating whether specifies that a schema is deprecated and SHOULD be transitioned out of usage. /// Default value is false. /// - public bool Deprecated { get; set; } + public virtual bool Deprecated { get; set; } - /// - public bool UnresolvedReference { get; set; } + public virtual IDictionary Extensions { get; set; } = new Dictionary(); - /// - public AsyncApiReference Reference { get; set; } - - public IDictionary Extensions { get; set; } = new Dictionary(); - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); @@ -420,55 +414,5 @@ public void SerializeV2WithoutReference(IAsyncApiWriter writer) writer.WriteEndObject(); } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - var target = this; - - var settings = writer.GetSettings(); - - if (this.Reference != null) - { - if (!settings.ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - // If Loop is detected then just Serialize as a reference. - if (!settings.LoopDetector.PushLoop(this)) - { - settings.LoopDetector.SaveLoop(this); - this.Reference.SerializeV2(writer); - return; - } - - target = this.GetReferenced(this.Reference.HostDocument); - } - - target.SerializeV2WithoutReference(writer); - - if (this.Reference != null) - { - settings.LoopDetector.PopLoop(); - } - } - - public AsyncApiJsonSchema GetReferenced(AsyncApiDocument document) - { - if (this.Reference != null && document != null) - { - return document.ResolveReference(this.Reference); - } - else - { - return this; - } - } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs new file mode 100644 index 00000000..624decd2 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs @@ -0,0 +1,315 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiJsonSchemaPayloadReference : AsyncApiJsonSchemaPayload, IAsyncApiReferenceable +{ + private AsyncApiJsonSchemaPayload target; + + private AsyncApiJsonSchemaPayload Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiJsonSchemaPayloadReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Schema); + } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public override string Title + { + get => this.Target?.Title; + set => this.Target.Title = value; + } + + public override SchemaType? Type + { + get => this.Target?.Type; + set => this.Target.Type = value; + } + + public override string Format + { + get => this.Target?.Format; + set => this.Target.Format = value; + } + + public override string Description + { + get => this.Target?.Description; + set => this.Target.Description = value; + } + + public override double? Maximum + { + get => this.Target?.Maximum; + set => this.Target.Maximum = value; + } + + public override double? ExclusiveMaximum + { + get => this.Target?.ExclusiveMaximum; + set => this.Target.ExclusiveMaximum = value; + } + + public override double? Minimum + { + get => this.Target?.Minimum; + set => this.Target.Minimum = value; + } + + public override double? ExclusiveMinimum + { + get => this.Target?.ExclusiveMinimum; + set => this.Target.ExclusiveMinimum = value; + } + + public override int? MaxLength + { + get => this.Target?.MaxLength; + set => this.Target.MaxLength = value; + } + + public override int? MinLength + { + get => this.Target?.MinLength; + set => this.Target.MinLength = value; + } + + public override string Pattern + { + get => this.Target?.Pattern; + set => this.Target.Pattern = value; + } + + public override double? MultipleOf + { + get => this.Target?.MultipleOf; + set => this.Target.MultipleOf = value; + } + + public override AsyncApiAny Default + { + get => this.Target?.Default; + set => this.Target.Default = value; + } + + public override bool ReadOnly + { + get => this.Target.ReadOnly; + set => this.Target.ReadOnly = value; + } + + public override bool WriteOnly + { + get => this.Target.WriteOnly; + set => this.Target.WriteOnly = value; + } + + public override IList AllOf + { + get => this.Target?.AllOf; + set => this.Target.AllOf = value; + } + + public override IList OneOf + { + get => this.Target?.OneOf; + set => this.Target.OneOf = value; + } + + public override IList AnyOf + { + get => this.Target?.AnyOf; + set => this.Target.AnyOf = value; + } + + public override AsyncApiJsonSchema Not + { + get => this.Target?.Not; + set => this.Target.Not = value; + } + + public override AsyncApiJsonSchema Contains + { + get => this.Target?.Contains; + set => this.Target.Contains = value; + } + + public override AsyncApiJsonSchema If + { + get => this.Target?.If; + set => this.Target.If = value; + } + + public override AsyncApiJsonSchema Then + { + get => this.Target?.Then; + set => this.Target.Then = value; + } + + public override AsyncApiJsonSchema Else + { + get => this.Target?.Else; + set => this.Target.Else = value; + } + + public override ISet Required + { + get => this.Target?.Required; + set => this.Target.Required = value; + } + + public override AsyncApiJsonSchema Items + { + get => this.Target?.Items; + set => this.Target.Items = value; + } + + public override AsyncApiJsonSchema AdditionalItems + { + get => this.Target?.AdditionalItems; + set => this.Target.AdditionalItems = value; + } + + public override int? MaxItems + { + get => this.Target?.MaxItems; + set => this.Target.MaxItems = value; + } + + public override int? MinItems + { + get => this.Target?.MinItems; + set => this.Target.MinItems = value; + } + + public override bool? UniqueItems + { + get => this.Target?.UniqueItems; + set => this.Target.UniqueItems = value; + } + + public override IDictionary Properties + { + get => this.Target?.Properties; + set => this.Target.Properties = value; + } + + public override int? MaxProperties + { + get => this.Target?.MaxProperties; + set => this.Target.MaxProperties = value; + } + + public override int? MinProperties + { + get => this.Target?.MinProperties; + set => this.Target.MinProperties = value; + } + + public override AsyncApiJsonSchema AdditionalProperties + { + get => this.Target?.AdditionalProperties; + set => this.Target.AdditionalProperties = value; + } + + public override IDictionary PatternProperties + { + get => this.Target?.PatternProperties; + set => this.Target.PatternProperties = value; + } + + public override AsyncApiJsonSchema PropertyNames + { + get => this.Target?.PropertyNames; + set => this.Target.PropertyNames = value; + } + + public override string Discriminator + { + get => this.Target?.Discriminator; + set => this.Target.Discriminator = value; + } + + public override IList Enum + { + get => this.Target?.Enum; + set => this.Target.Enum = value; + } + + public override IList Examples + { + get => this.Target?.Examples; + set => this.Target.Examples = value; + } + + public override AsyncApiAny Const + { + get => this.Target?.Const; + set => this.Target.Const = value; + } + + public override bool Nullable + { + get => this.Target.Nullable; + set => this.Target.Nullable = value; + } + + public override AsyncApiExternalDocumentation ExternalDocs + { + get => this.Target?.ExternalDocs; + set => this.Target.ExternalDocs = value; + } + + public override bool Deprecated + { + get => this.Target.Deprecated; + set => this.Target.Deprecated = value; + } + + public override IDictionary Extensions + { + get => this.Target?.Extensions; + set => this.Target.Extensions = value; + } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + var settings = writer.GetSettings(); + if (!settings.ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + + // If Loop is detected then just Serialize as a reference. + if (!settings.LoopDetector.PushLoop(this)) + { + settings.LoopDetector.SaveLoop(this); + this.Reference.SerializeV2(writer); + return; + } + + this.Target.SerializeV2(writer); + } +} +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs new file mode 100644 index 00000000..6a883435 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs @@ -0,0 +1,318 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiJsonSchemaReference : AsyncApiJsonSchema, IAsyncApiReferenceable + { + private AsyncApiJsonSchema target; + + private AsyncApiJsonSchema Target + { + get + { + this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiJsonSchemaReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Schema); + } + + public AsyncApiReference Reference { get; set; } + + public bool UnresolvedReference + { + get { return this.Target == null; } + } + + public override string Title + { + get => this.Target?.Title; + set => this.Target.Title = value; + } + + public override SchemaType? Type + { + get => this.Target?.Type; + set => this.Target.Type = value; + } + + public override string Format + { + get => this.Target?.Format; + set => this.Target.Format = value; + } + + public override string Description + { + get => this.Target?.Description; + set => this.Target.Description = value; + } + + public override double? Maximum + { + get => this.Target?.Maximum; + set => this.Target.Maximum = value; + } + + public override double? ExclusiveMaximum + { + get => this.Target?.ExclusiveMaximum; + set => this.Target.ExclusiveMaximum = value; + } + + public override double? Minimum + { + get => this.Target?.Minimum; + set => this.Target.Minimum = value; + } + + public override double? ExclusiveMinimum + { + get => this.Target?.ExclusiveMinimum; + set => this.Target.ExclusiveMinimum = value; + } + + public override int? MaxLength + { + get => this.Target?.MaxLength; + set => this.Target.MaxLength = value; + } + + public override int? MinLength + { + get => this.Target?.MinLength; + set => this.Target.MinLength = value; + } + + public override string Pattern + { + get => this.Target?.Pattern; + set => this.Target.Pattern = value; + } + + public override double? MultipleOf + { + get => this.Target?.MultipleOf; + set => this.Target.MultipleOf = value; + } + + public override AsyncApiAny Default + { + get => this.Target?.Default; + set => this.Target.Default = value; + } + + public override bool ReadOnly + { + get => this.Target.ReadOnly; + set => this.Target.ReadOnly = value; + } + + public override bool WriteOnly + { + get => this.Target.WriteOnly; + set => this.Target.WriteOnly = value; + } + + public override IList AllOf + { + get => this.Target?.AllOf; + set => this.Target.AllOf = value; + } + + public override IList OneOf + { + get => this.Target?.OneOf; + set => this.Target.OneOf = value; + } + + public override IList AnyOf + { + get => this.Target?.AnyOf; + set => this.Target.AnyOf = value; + } + + public override AsyncApiJsonSchema Not + { + get => this.Target?.Not; + set => this.Target.Not = value; + } + + public override AsyncApiJsonSchema Contains + { + get => this.Target?.Contains; + set => this.Target.Contains = value; + } + + public override AsyncApiJsonSchema If + { + get => this.Target?.If; + set => this.Target.If = value; + } + + public override AsyncApiJsonSchema Then + { + get => this.Target?.Then; + set => this.Target.Then = value; + } + + public override AsyncApiJsonSchema Else + { + get => this.Target?.Else; + set => this.Target.Else = value; + } + + public override ISet Required + { + get => this.Target?.Required; + set => this.Target.Required = value; + } + + public override AsyncApiJsonSchema Items + { + get => this.Target?.Items; + set => this.Target.Items = value; + } + + public override AsyncApiJsonSchema AdditionalItems + { + get => this.Target?.AdditionalItems; + set => this.Target.AdditionalItems = value; + } + + public override int? MaxItems + { + get => this.Target?.MaxItems; + set => this.Target.MaxItems = value; + } + + public override int? MinItems + { + get => this.Target?.MinItems; + set => this.Target.MinItems = value; + } + + public override bool? UniqueItems + { + get => this.Target?.UniqueItems; + set => this.Target.UniqueItems = value; + } + + public override IDictionary Properties + { + get => this.Target?.Properties; + set => this.Target.Properties = value; + } + + public override int? MaxProperties + { + get => this.Target?.MaxProperties; + set => this.Target.MaxProperties = value; + } + + public override int? MinProperties + { + get => this.Target?.MinProperties; + set => this.Target.MinProperties = value; + } + + public override AsyncApiJsonSchema AdditionalProperties + { + get => this.Target?.AdditionalProperties; + set => this.Target.AdditionalProperties = value; + } + + public override IDictionary PatternProperties + { + get => this.Target?.PatternProperties; + set => this.Target.PatternProperties = value; + } + + public override AsyncApiJsonSchema PropertyNames + { + get => this.Target?.PropertyNames; + set => this.Target.PropertyNames = value; + } + + public override string Discriminator + { + get => this.Target?.Discriminator; + set => this.Target.Discriminator = value; + } + + public override IList Enum + { + get => this.Target?.Enum; + set => this.Target.Enum = value; + } + + public override IList Examples + { + get => this.Target?.Examples; + set => this.Target.Examples = value; + } + + public override AsyncApiAny Const + { + get => this.Target?.Const; + set => this.Target.Const = value; + } + + public override bool Nullable + { + get => this.Target.Nullable; + set => this.Target.Nullable = value; + } + + public override AsyncApiExternalDocumentation ExternalDocs + { + get => this.Target?.ExternalDocs; + set => this.Target.ExternalDocs = value; + } + + public override bool Deprecated + { + get => this.Target.Deprecated; + set => this.Target.Deprecated = value; + } + + public override IDictionary Extensions + { + get => this.Target?.Extensions; + set => this.Target.Extensions = value; + } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + var settings = writer.GetSettings(); + if (!settings.ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + + // If Loop is detected then just Serialize as a reference. + if (!settings.LoopDetector.PushLoop(this)) + { + settings.LoopDetector.SaveLoop(this); + this.Reference.SerializeV2(writer); + return; + } + + this.Target.SerializeV2(writer); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 7e33d3d7..2d62a414 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -336,8 +336,9 @@ internal void Walk(AsyncApiAvroSchemaPayload payload) internal void Walk(AsyncApiJsonSchema schema, bool isComponent = false) { - if (schema == null || this.ProcessAsReference(schema, isComponent)) + if (schema is AsyncApiJsonSchemaReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 7b9315af..74a7b32d 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -387,14 +387,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, - Payload = new AsyncApiJsonSchemaPayload - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "lightMeasuredPayload", - }, - }, + Payload = new AsyncApiJsonSchemaPayloadReference("#/components/schemas/lightMeasuredPayload"), }) .WithComponent("turnOnOff", new AsyncApiMessage() { @@ -405,14 +398,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, - Payload = new AsyncApiJsonSchemaPayload() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "turnOnOffPayload", - }, - }, + Payload = new AsyncApiJsonSchemaPayloadReference("#/components/schemas/turnOnOffPayload"), }) .WithComponent("dimLight", new AsyncApiMessage() { From 31f486809f42482e6576f0d86e5cb7f375419355 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Wed, 29 Jan 2025 12:32:50 +0100 Subject: [PATCH 12/20] fix writing via rootdocument for workspace. --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 4 +- .../V2/AsyncApiMessageDeserializer.cs | 4 +- .../V2/AsyncApiV2VersionService.cs | 6 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 1 + .../Models/AsyncApiAvroSchemaPayload.cs | 66 ---- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 5 +- .../Models/AsyncApiJsonSchemaPayload.cs | 109 ------ src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 1 - .../{AvroSchema.cs => AsyncApiAvroSchema.cs} | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 2 +- .../Models/Avro/AvroPrimitive.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 4 +- .../Models/JsonSchema/AsyncApiJsonSchema.cs | 2 +- .../Models/MessagePayloadExtensions.cs | 37 ++ .../References/AsyncApiChannelReference.cs | 3 +- .../AsyncApiCorrelationIdReference.cs | 3 +- .../AsyncApiJsonSchemaPayloadReference.cs | 315 ------------------ .../References/AsyncApiJsonSchemaReference.cs | 3 +- .../References/AsyncApiMessageReference.cs | 3 +- .../AsyncApiMessageTraitReference.cs | 3 +- .../AsyncApiOperationTraitReference.cs | 3 +- .../References/AsyncApiParameterReference.cs | 3 +- .../AsyncApiSecuritySchemeReference.cs | 3 +- .../References/AsyncApiServerReference.cs | 3 +- .../AsyncApiServerVariableReference.cs | 3 +- .../Services/AsyncApiVisitorBase.cs | 15 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 50 +-- .../Validation/AsyncApiValidator.cs | 8 +- .../Validation/Rules/AsyncApiAvroRules.cs | 60 ++-- .../Writers/AsyncApiWriterBase.cs | 3 + src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs | 4 + .../AsyncApiDocumentV2Tests.cs | 44 +-- .../AsyncApiReaderTests.cs | 2 +- .../Models/AsyncApiMessage_Should.cs | 18 +- .../Models/AsyncApiReference_Should.cs | 16 +- .../Models/AsyncApiSchema_Should.cs | 14 +- .../Models/AvroSchema_Should.cs | 12 +- 42 files changed, 197 insertions(+), 653 deletions(-) delete mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs delete mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs rename src/LEGO.AsyncAPI/Models/Avro/{AvroSchema.cs => AsyncApiAvroSchema.cs} (85%) create mode 100644 src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs delete mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 1ac2c611..2a4d9b59 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -212,7 +212,7 @@ public class AsyncApiAvroSchemaDeserializer { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, }; - public static AvroSchema LoadSchema(ParseNode node) + public static AsyncApiAvroSchema LoadSchema(ParseNode node) { if (node is ValueNode valueNode) { @@ -284,7 +284,7 @@ public static AvroSchema LoadSchema(ParseNode node) throw new AsyncApiReaderException("Invalid node type"); } - private static AvroSchema LoadLogicalType(MapNode mapNode) + private static AsyncApiAvroSchema LoadLogicalType(MapNode mapNode) { var type = mapNode["logicalType"]?.Value.GetScalarValue(); switch (type) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index daf867bf..fe7b8d4a 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -88,9 +88,9 @@ private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) case null: case "": case var _ when SupportedJsonSchemaFormats.Where(s => format.StartsWith(s)).Any(): - return new AsyncApiJsonSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n)); + return AsyncApiSchemaDeserializer.LoadSchema(n); case var _ when SupportedAvroSchemaFormats.Where(s => format.StartsWith(s)).Any(): - return new AsyncApiAvroSchemaPayload(AsyncApiAvroSchemaDeserializer.LoadSchema(n)); + return AsyncApiAvroSchemaDeserializer.LoadSchema(n); default: var supportedFormats = SupportedJsonSchemaFormats.Concat(SupportedAvroSchemaFormats); throw new AsyncApiException($"'Could not deserialize Payload. Supported formats are {string.Join(", ", supportedFormats)}"); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index ae7cc530..80701d41 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -35,9 +35,9 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation, [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, [typeof(AsyncApiJsonSchema)] = AsyncApiSchemaDeserializer.LoadSchema, - [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, - [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, - [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, + [typeof(AsyncApiAvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, + [typeof(AsyncApiJsonSchema)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, + [typeof(AsyncApiAvroSchema)] = AsyncApiV2Deserializer.LoadAvroPayload, [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, [typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme, [typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer, diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index ea6572e6..9bcf8eeb 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -71,6 +71,7 @@ public void RegisterComponents(AsyncApiDocument document) { location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; this.RegisterComponent(location, item.Value); + this.RegisterComponent(item.Key, item.Value); } // Register Parameters diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs deleted file mode 100644 index 0e9d1cb9..00000000 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) The LEGO Group. All rights reserved. - -namespace LEGO.AsyncAPI.Models -{ - using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; - using System; - - public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload - { - private readonly AvroSchema schema; - - public AsyncApiAvroSchemaPayload(AvroSchema schema) - { - this.schema = schema; - } - - public AsyncApiAvroSchemaPayload() - { - this.schema = new AvroRecord(); - } - - public bool TryGetAs(out T schema) - where T : AvroSchema - { - schema = this.schema as T; - return schema != null; - } - - public bool Is() - where T : AvroSchema - { - return this.schema is T; - } - - public Type GetSchemaType() - { - return this.schema.GetType(); - } - - public bool UnresolvedReference { get => this.schema.UnresolvedReference; set => this.schema.UnresolvedReference = value; } - - public AsyncApiReference Reference { get => this.schema.Reference; set => this.schema.Reference = value; } - - public void SerializeV2(IAsyncApiWriter writer) - { - var settings = writer.GetSettings(); - - if (this.Reference != null) - { - if (!settings.ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) - { - this.schema.SerializeV2(writer); - } - } -} diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 89edf733..5aad6e40 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -12,7 +12,7 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable { - internal AsyncApiWorkspace Workspace { get; set; } + internal AsyncApiWorkspace Workspace { get; set; } = new AsyncApiWorkspace(); /// /// REQUIRED. Specifies the AsyncAPI Specification version being used. @@ -73,6 +73,9 @@ public void SerializeV2(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } + this.Workspace.RegisterComponents(this); + + writer.RootDocument = this; writer.WriteStartObject(); // asyncApi diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs deleted file mode 100644 index b13e258b..00000000 --- a/src/LEGO.AsyncAPI/Models/AsyncApiJsonSchemaPayload.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) The LEGO Group. All rights reserved. - -namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload { private readonly AsyncApiJsonSchema schema; - - public AsyncApiJsonSchemaPayload() - { - this.schema = new AsyncApiJsonSchema(); - } - - public AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) - { - this.schema = schema; - } - - public virtual string Title { get => this.schema.Title; set => this.schema.Title = value; } - - public virtual SchemaType? Type { get => this.schema.Type; set => this.schema.Type = value; } - - public virtual string Format { get => this.schema.Format; set => this.schema.Format = value; } - - public virtual string Description { get => this.schema.Description; set => this.schema.Description = value; } - - public virtual double? Maximum { get => this.schema.Maximum; set => this.schema.Maximum = value; } - - public virtual double? ExclusiveMaximum { get => this.schema.ExclusiveMaximum; set => this.schema.ExclusiveMaximum = value; } - - public virtual double? Minimum { get => this.schema.Minimum; set => this.schema.Minimum = value; } - - public virtual double? ExclusiveMinimum { get => this.schema.ExclusiveMinimum; set => this.schema.ExclusiveMinimum = value; } - - public virtual int? MaxLength { get => this.schema.MaxLength; set => this.schema.MaxLength = value; } - - public virtual int? MinLength { get => this.schema.MinLength; set => this.schema.MinLength = value; } - - public virtual string Pattern { get => this.schema.Pattern; set => this.schema.Pattern = value; } - - public virtual double? MultipleOf { get => this.schema.MultipleOf; set => this.schema.MultipleOf = value; } - - public virtual AsyncApiAny Default { get => this.schema.Default; set => this.schema.Default = value; } - - public virtual bool ReadOnly { get => this.schema.ReadOnly; set => this.schema.ReadOnly = value; } - - public virtual bool WriteOnly { get => this.schema.WriteOnly; set => this.schema.WriteOnly = value; } - - public virtual IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; } - - public virtual IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; } - - public virtual IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; } - - public virtual AsyncApiJsonSchema Not { get => this.schema.Not; set => this.schema.Not = value; } - - public virtual AsyncApiJsonSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } - - public virtual AsyncApiJsonSchema If { get => this.schema.If; set => this.schema.If = value; } - - public virtual AsyncApiJsonSchema Then { get => this.schema.Then; set => this.schema.Then = value; } - - public virtual AsyncApiJsonSchema Else { get => this.schema.Else; set => this.schema.Else = value; } - - public virtual ISet Required { get => this.schema.Required; set => this.schema.Required = value; } - - public virtual AsyncApiJsonSchema Items { get => this.schema.Items; set => this.schema.Items = value; } - - public virtual AsyncApiJsonSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; } - - public virtual int? MaxItems { get => this.schema.MaxItems; set => this.schema.MaxItems = value; } - - public virtual int? MinItems { get => this.schema.MinItems; set => this.schema.MinItems = value; } - - public virtual bool? UniqueItems { get => this.schema.UniqueItems; set => this.schema.UniqueItems = value; } - - public virtual IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; } - - public virtual int? MaxProperties { get => this.schema.MaxProperties; set => this.schema.MaxProperties = value; } - - public virtual int? MinProperties { get => this.schema.MinProperties; set => this.schema.MinProperties = value; } - - public virtual IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; } - - public virtual AsyncApiJsonSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; } - - public virtual string Discriminator { get => this.schema.Discriminator; set => this.schema.Discriminator = value; } - - public virtual IList Enum { get => this.schema.Enum; set => this.schema.Enum = value; } - - public virtual IList Examples { get => this.schema.Examples; set => this.schema.Examples = value; } - - public virtual AsyncApiAny Const { get => this.schema.Const; set => this.schema.Const = value; } - - public virtual bool Nullable { get => this.schema.Nullable; set => this.schema.Nullable = value; } - - public virtual AsyncApiExternalDocumentation ExternalDocs { get => this.schema.ExternalDocs; set => this.schema.ExternalDocs = value; } - - public virtual bool Deprecated { get => this.schema.Deprecated; set => this.schema.Deprecated = value; } - - public virtual IDictionary Extensions { get => this.schema.Extensions; set => this.schema.Extensions = value; } - - public virtual AsyncApiJsonSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } - - public static implicit operator AsyncApiJsonSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; - - public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiJsonSchema schema) => new AsyncApiJsonSchemaPayload(schema); - - public virtual void SerializeV2(IAsyncApiWriter writer) - { - this.schema.SerializeV2(writer); - } - } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index c0292bb2..69e819ee 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -155,7 +155,6 @@ public void SerializeV2(IAsyncApiWriter writer) private string GetExternalReferenceV2() { - return this.ExternalResource + (this.FragmentId != null ? "#" + this.FragmentId : string.Empty); } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs similarity index 85% rename from src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs rename to src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs index 081995eb..d9200657 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs @@ -7,7 +7,7 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable + public abstract class AsyncApiAvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable, IAsyncApiMessagePayload { public abstract string Type { get; } @@ -20,7 +20,7 @@ public abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable public AsyncApiReference Reference { get; set; } - public static implicit operator AvroSchema(AvroPrimitiveType type) + public static implicit operator AsyncApiAvroSchema(AvroPrimitiveType type) { return new AvroPrimitive(type); } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 8aa28144..b627bde7 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -6,14 +6,14 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroArray : AvroSchema + public class AvroArray : AsyncApiAvroSchema { public override string Type { get; } = "array"; /// /// The schema of the array's items. /// - public AvroSchema Items { get; set; } + public AsyncApiAvroSchema Items { get; set; } /// /// A map of properties not in the schema, but added as additional metadata. diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index e7428c21..6f984c39 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroEnum : AvroSchema + public class AvroEnum : AsyncApiAvroSchema { public override string Type { get; } = "enum"; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 659f1a4a..15763861 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -34,7 +34,7 @@ public class AvroField : IAsyncApiSerializable /// /// The type of the field. Can be a primitive type, a complex type, or a union. /// - public AvroSchema Type { get; set; } + public AsyncApiAvroSchema Type { get; set; } /// /// The documentation for the field. diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 36da6f0c..f11b5164 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroFixed : AvroSchema + public class AvroFixed : AsyncApiAvroSchema { public override string Type { get; } = "fixed"; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 934d4dec..9752e453 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroMap : AvroSchema + public class AvroMap : AsyncApiAvroSchema { public override string Type { get; } = "map"; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 1f8006ff..c59a88ca 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroPrimitive : AvroSchema + public class AvroPrimitive : AsyncApiAvroSchema { public override string Type { get; } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index a5ea1cee..4e2e27a1 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroRecord : AvroSchema + public class AvroRecord : AsyncApiAvroSchema { public override string Type { get; } = "record"; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs index 19ad3a3d..9ef2dd37 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -6,14 +6,14 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroUnion : AvroSchema + public class AvroUnion : AsyncApiAvroSchema { public override string Type { get; } = "map"; /// /// The types in this union. /// - public IList Types { get; set; } = new List(); + public IList Types { get; set; } = new List(); /// /// A map of properties not in the schema, but added as additional metadata. diff --git a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs index d316c906..3d36eaf1 100644 --- a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs +++ b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs @@ -11,7 +11,7 @@ namespace LEGO.AsyncAPI.Models /// /// The Schema Object allows the definition of input and output data types. /// - public class AsyncApiJsonSchema : IAsyncApiExtensible, IAsyncApiSerializable + public class AsyncApiJsonSchema : IAsyncApiExtensible, IAsyncApiSerializable, IAsyncApiMessagePayload { /// /// follow JSON Schema definition. Short text providing information about the data. diff --git a/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs new file mode 100644 index 00000000..8aa21a6d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + using System; + + public static class MessagePayloadExtensions + { + public static bool TryGetAs(this IAsyncApiMessagePayload payload, out T result) + where T : class, IAsyncApiMessagePayload + { + result = payload as T; + return result != null; + } + + public static bool TryGetAs(this AsyncApiAvroSchema avroSchema, out T result) + where T : AsyncApiAvroSchema + { + result = avroSchema as T; + return result != null; + } + + public static bool Is(this IAsyncApiMessagePayload payload) + where T : class, IAsyncApiMessagePayload + { + return payload is T; + } + + public static bool Is(this AsyncApiAvroSchema schema) + where T : AsyncApiAvroSchema + { + return schema is T; + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs index dd4aa4e8..504dbf00 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs @@ -14,7 +14,7 @@ private AsyncApiChannel Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -51,6 +51,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs index 5bc56e2b..e423d8e3 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs @@ -17,7 +17,7 @@ private AsyncApiCorrelationId Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -46,6 +46,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs deleted file mode 100644 index 624decd2..00000000 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaPayloadReference.cs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright (c) The LEGO Group. All rights reserved. - -namespace LEGO.AsyncAPI.Models -{ - using System; - using System.Collections.Generic; - using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; - - public class AsyncApiJsonSchemaPayloadReference : AsyncApiJsonSchemaPayload, IAsyncApiReferenceable -{ - private AsyncApiJsonSchemaPayload target; - - private AsyncApiJsonSchemaPayload Target - { - get - { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); - return this.target; - } - } - - public AsyncApiJsonSchemaPayloadReference(string reference) - { - this.Reference = new AsyncApiReference(reference, ReferenceType.Schema); - } - - public AsyncApiReference Reference { get; set; } - - public bool UnresolvedReference { get { return this.Target == null; } } - - public override string Title - { - get => this.Target?.Title; - set => this.Target.Title = value; - } - - public override SchemaType? Type - { - get => this.Target?.Type; - set => this.Target.Type = value; - } - - public override string Format - { - get => this.Target?.Format; - set => this.Target.Format = value; - } - - public override string Description - { - get => this.Target?.Description; - set => this.Target.Description = value; - } - - public override double? Maximum - { - get => this.Target?.Maximum; - set => this.Target.Maximum = value; - } - - public override double? ExclusiveMaximum - { - get => this.Target?.ExclusiveMaximum; - set => this.Target.ExclusiveMaximum = value; - } - - public override double? Minimum - { - get => this.Target?.Minimum; - set => this.Target.Minimum = value; - } - - public override double? ExclusiveMinimum - { - get => this.Target?.ExclusiveMinimum; - set => this.Target.ExclusiveMinimum = value; - } - - public override int? MaxLength - { - get => this.Target?.MaxLength; - set => this.Target.MaxLength = value; - } - - public override int? MinLength - { - get => this.Target?.MinLength; - set => this.Target.MinLength = value; - } - - public override string Pattern - { - get => this.Target?.Pattern; - set => this.Target.Pattern = value; - } - - public override double? MultipleOf - { - get => this.Target?.MultipleOf; - set => this.Target.MultipleOf = value; - } - - public override AsyncApiAny Default - { - get => this.Target?.Default; - set => this.Target.Default = value; - } - - public override bool ReadOnly - { - get => this.Target.ReadOnly; - set => this.Target.ReadOnly = value; - } - - public override bool WriteOnly - { - get => this.Target.WriteOnly; - set => this.Target.WriteOnly = value; - } - - public override IList AllOf - { - get => this.Target?.AllOf; - set => this.Target.AllOf = value; - } - - public override IList OneOf - { - get => this.Target?.OneOf; - set => this.Target.OneOf = value; - } - - public override IList AnyOf - { - get => this.Target?.AnyOf; - set => this.Target.AnyOf = value; - } - - public override AsyncApiJsonSchema Not - { - get => this.Target?.Not; - set => this.Target.Not = value; - } - - public override AsyncApiJsonSchema Contains - { - get => this.Target?.Contains; - set => this.Target.Contains = value; - } - - public override AsyncApiJsonSchema If - { - get => this.Target?.If; - set => this.Target.If = value; - } - - public override AsyncApiJsonSchema Then - { - get => this.Target?.Then; - set => this.Target.Then = value; - } - - public override AsyncApiJsonSchema Else - { - get => this.Target?.Else; - set => this.Target.Else = value; - } - - public override ISet Required - { - get => this.Target?.Required; - set => this.Target.Required = value; - } - - public override AsyncApiJsonSchema Items - { - get => this.Target?.Items; - set => this.Target.Items = value; - } - - public override AsyncApiJsonSchema AdditionalItems - { - get => this.Target?.AdditionalItems; - set => this.Target.AdditionalItems = value; - } - - public override int? MaxItems - { - get => this.Target?.MaxItems; - set => this.Target.MaxItems = value; - } - - public override int? MinItems - { - get => this.Target?.MinItems; - set => this.Target.MinItems = value; - } - - public override bool? UniqueItems - { - get => this.Target?.UniqueItems; - set => this.Target.UniqueItems = value; - } - - public override IDictionary Properties - { - get => this.Target?.Properties; - set => this.Target.Properties = value; - } - - public override int? MaxProperties - { - get => this.Target?.MaxProperties; - set => this.Target.MaxProperties = value; - } - - public override int? MinProperties - { - get => this.Target?.MinProperties; - set => this.Target.MinProperties = value; - } - - public override AsyncApiJsonSchema AdditionalProperties - { - get => this.Target?.AdditionalProperties; - set => this.Target.AdditionalProperties = value; - } - - public override IDictionary PatternProperties - { - get => this.Target?.PatternProperties; - set => this.Target.PatternProperties = value; - } - - public override AsyncApiJsonSchema PropertyNames - { - get => this.Target?.PropertyNames; - set => this.Target.PropertyNames = value; - } - - public override string Discriminator - { - get => this.Target?.Discriminator; - set => this.Target.Discriminator = value; - } - - public override IList Enum - { - get => this.Target?.Enum; - set => this.Target.Enum = value; - } - - public override IList Examples - { - get => this.Target?.Examples; - set => this.Target.Examples = value; - } - - public override AsyncApiAny Const - { - get => this.Target?.Const; - set => this.Target.Const = value; - } - - public override bool Nullable - { - get => this.Target.Nullable; - set => this.Target.Nullable = value; - } - - public override AsyncApiExternalDocumentation ExternalDocs - { - get => this.Target?.ExternalDocs; - set => this.Target.ExternalDocs = value; - } - - public override bool Deprecated - { - get => this.Target.Deprecated; - set => this.Target.Deprecated = value; - } - - public override IDictionary Extensions - { - get => this.Target?.Extensions; - set => this.Target.Extensions = value; - } - - public override void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - var settings = writer.GetSettings(); - if (!settings.ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - // If Loop is detected then just Serialize as a reference. - if (!settings.LoopDetector.PushLoop(this)) - { - settings.LoopDetector.SaveLoop(this); - this.Reference.SerializeV2(writer); - return; - } - - this.Target.SerializeV2(writer); - } -} -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs index 6a883435..a3b7d729 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs @@ -15,7 +15,7 @@ private AsyncApiJsonSchema Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -304,6 +304,7 @@ public override void SerializeV2(IAsyncApiWriter writer) return; } + this.Reference.HostDocument = writer.RootDocument; // If Loop is detected then just Serialize as a reference. if (!settings.LoopDetector.PushLoop(this)) { diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs index 09bc4fe2..2e6dbdf2 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -17,7 +17,7 @@ private AsyncApiMessage Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -72,6 +72,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs index 9beefe3b..76cc38db 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs @@ -17,7 +17,7 @@ private AsyncApiMessageTrait Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -68,6 +68,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs index 6968cc0c..92bee6fe 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs @@ -17,7 +17,7 @@ private AsyncApiOperationTrait Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -54,6 +54,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs index 65d23d0d..8ae13fc3 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs @@ -17,7 +17,7 @@ private AsyncApiParameter Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -48,6 +48,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs index a38d95df..75b8a189 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -18,7 +18,7 @@ private AsyncApiSecurityScheme Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -59,6 +59,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs index 66f163f6..4927b3f2 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -17,7 +17,7 @@ private AsyncApiServer Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -58,6 +58,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs index a6fbc3cf..82cb6ae0 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs @@ -17,7 +17,7 @@ private AsyncApiServerVariable Target { get { - this.target ??= this.Reference.HostDocument.ResolveReference(this.Reference); + this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); return this.target; } } @@ -49,6 +49,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { + this.Reference.HostDocument = writer.RootDocument; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs index a4f7b0b1..09ffe62e 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -55,11 +55,15 @@ public virtual void Visit(AsyncApiDocument doc) { } - public virtual void Visit(AsyncApiJsonSchemaPayload jsonPayload) + public virtual void Visit(IAsyncApiMessagePayload payload) { } - public virtual void Visit(AsyncApiAvroSchemaPayload avroPayload) + public virtual void Visit(AsyncApiAvroSchema item) + { + } + + public virtual void Visit(AsyncApiJsonSchema item) { } @@ -152,13 +156,6 @@ public virtual void Visit(AsyncApiExternalDocumentation externalDocs) { } - /// - /// Visits . - /// - public virtual void Visit(AsyncApiJsonSchema schema) - { - } - public virtual void Visit(AsyncApiMessage message) { } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 2d62a414..24a81a57 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -271,23 +271,21 @@ internal void Walk(AsyncApiExternalDocumentation externalDocs) internal void Walk(AsyncApiChannel channel, bool isComponent = false) { + if (channel == null) + { + return; + } if (channel is AsyncApiChannelReference) { this.Walk(channel as IAsyncApiReferenceable); return; } - this.visitor.Visit(channel); - - if (channel != null) - { - this.Walk(AsyncApiConstants.Subscribe, () => this.Walk(channel.Subscribe)); - this.Walk(AsyncApiConstants.Publish, () => this.Walk(channel.Publish)); - - this.Walk(AsyncApiConstants.Bindings, () => this.Walk(channel.Bindings)); - this.Walk(AsyncApiConstants.Parameters, () => this.Walk(channel.Parameters)); - } + this.Walk(AsyncApiConstants.Subscribe, () => this.Walk(channel.Subscribe)); + this.Walk(AsyncApiConstants.Publish, () => this.Walk(channel.Publish)); + this.Walk(AsyncApiConstants.Bindings, () => this.Walk(channel.Bindings)); + this.Walk(AsyncApiConstants.Parameters, () => this.Walk(channel.Parameters)); this.Walk(channel as IAsyncApiExtensible); } @@ -329,13 +327,32 @@ internal void Walk(AsyncApiParameter parameter, bool isComponent = false) this.Walk(parameter as IAsyncApiExtensible); } - internal void Walk(AsyncApiAvroSchemaPayload payload) + internal void Walk(IAsyncApiMessagePayload payload) { this.visitor.Visit(payload); + if (payload is AsyncApiJsonSchema jsonSchema) + { + this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiJsonSchema)payload)); + } + + if (payload is AsyncApiAvroSchema avroPayload) + { + this.Walk(AsyncApiConstants.Payload, () => this.Walk(avroPayload)); + } + } + + internal void Walk(AsyncApiAvroSchema schema) + { + this.visitor.Visit(schema); } internal void Walk(AsyncApiJsonSchema schema, bool isComponent = false) { + if (schema == null) + { + return; + } + if (schema is AsyncApiJsonSchemaReference reference) { this.Walk(reference as IAsyncApiReferenceable); @@ -545,16 +562,7 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) if (message != null) { this.Walk(AsyncApiConstants.Headers, () => this.Walk(message.Headers)); - if (message.Payload is AsyncApiJsonSchemaPayload payload) - { - this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiJsonSchema)payload)); - } - - if (message.Payload is AsyncApiAvroSchemaPayload avroPayload) - { - this.Walk(AsyncApiConstants.Payload, () => this.Walk(avroPayload)); - } - + this.Walk(message.Payload); this.Walk(AsyncApiConstants.CorrelationId, () => this.Walk(message.CorrelationId)); this.Walk(AsyncApiConstants.Tags, () => this.Walk(message.Tags)); this.Walk(AsyncApiConstants.Examples, () => this.Walk(message.Examples)); diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index 0da05afc..95ecf5c5 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -130,6 +130,10 @@ public void AddWarning(AsyncApiValidatorWarning warning) /// The object to be validated. public override void Visit(AsyncApiJsonSchema item) => this.Validate(item); + public override void Visit(AsyncApiAvroSchema item) => this.Validate(item); + + public override void Visit(IAsyncApiMessagePayload item) => this.Validate(item); + /// /// Execute validation rules against an . /// @@ -144,10 +148,6 @@ public void AddWarning(AsyncApiValidatorWarning warning) public override void Visit(IMessageBinding item) => this.Validate(item); - public override void Visit(AsyncApiAvroSchemaPayload item) => this.Validate(item); - - public override void Visit(AsyncApiJsonSchemaPayload item) => this.Validate(item); - /// /// Execute validation rules against an . /// diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs index b12c88b2..364986bf 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs @@ -5,53 +5,57 @@ namespace LEGO.AsyncAPI.Validation.Rules using System.Linq; using System.Text.RegularExpressions; using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Validations; [AsyncApiRule] - public static class AsyncApiAvroRules + public static class AsyncApiMessagePayloadRules { /// /// The key regex. /// public static Regex NameRegex = new Regex(@"^[A-Za-z_][A-Za-z0-9_]*$"); - public static ValidationRule NameRegularExpression => - new ValidationRule( - (context, avroPayload) => + public static ValidationRule NameRegularExpression => + new ValidationRule( + (context, messagePayload) => { string name = null; context.Enter("name"); - if (avroPayload.TryGetAs(out var record)) + if (messagePayload is AsyncApiAvroSchema avroPayload) { - name = record.Name; - } + if (avroPayload.TryGetAs(out var record)) + { + name = record.Name; + } - if (avroPayload.TryGetAs(out var @enum)) - { - name = @enum.Name; - if (@enum.Symbols.Any(symbol => !NameRegex.IsMatch(symbol))) + if (avroPayload.TryGetAs(out var @enum)) { - context.CreateError( - "SymbolsRegularExpression", - string.Format(Resource.Validation_SymbolsMustMatchRegularExpression, NameRegex.ToString())); + name = @enum.Name; + if (@enum.Symbols.Any(symbol => !NameRegex.IsMatch(symbol))) + { + context.CreateError( + "SymbolsRegularExpression", + string.Format(Resource.Validation_SymbolsMustMatchRegularExpression, NameRegex.ToString())); + } } - } - if (avroPayload.TryGetAs(out var @fixed)) - { - name = @fixed.Name; - } + if (avroPayload.TryGetAs(out var @fixed)) + { + name = @fixed.Name; + } - if (name == null) - { - return; - } + if (name == null) + { + return; + } - if (!NameRegex.IsMatch(record.Name)) - { - context.CreateError( - nameof(NameRegex), - string.Format(Resource.Validation_NameMustMatchRegularExpr, name, NameRegex.ToString())); + if (!NameRegex.IsMatch(record.Name)) + { + context.CreateError( + nameof(NameRegex), + string.Format(Resource.Validation_NameMustMatchRegularExpr, name, NameRegex.ToString())); + } } context.Exit(); diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs index 7cc6ee02..aa123d7b 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs @@ -2,6 +2,7 @@ namespace LEGO.AsyncAPI.Writers { + using LEGO.AsyncAPI.Models; using System; using System.Collections.Generic; using System.IO; @@ -67,6 +68,8 @@ public AsyncApiWriterBase(TextWriter textWriter, AsyncApiWriterSettings settings /// protected TextWriter Writer { get; } + public AsyncApiDocument RootDocument { get; set; } + /// /// Write start object. /// diff --git a/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs b/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs index 7faff47f..7116a7c5 100644 --- a/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs +++ b/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs @@ -1,9 +1,13 @@ // Copyright (c) The LEGO Group. All rights reserved. +using LEGO.AsyncAPI.Models; + namespace LEGO.AsyncAPI.Writers { public interface IAsyncApiWriter { + AsyncApiDocument RootDocument { get; set; } + /// /// Write the start object. /// diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 74a7b32d..53434e55 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -387,7 +387,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, - Payload = new AsyncApiJsonSchemaPayloadReference("#/components/schemas/lightMeasuredPayload"), + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/lightMeasuredPayload"), }) .WithComponent("turnOnOff", new AsyncApiMessage() { @@ -398,7 +398,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, - Payload = new AsyncApiJsonSchemaPayloadReference("#/components/schemas/turnOnOffPayload"), + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/turnOnOffPayload"), }) .WithComponent("dimLight", new AsyncApiMessage() { @@ -409,14 +409,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() { new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, - Payload = new AsyncApiJsonSchemaPayload() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "dimLightPayload", - }, - }, + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/dimLightPayload"), }) .WithComponent("lightMeasuredPayload", new AsyncApiJsonSchema() { @@ -432,14 +425,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -461,14 +447,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -487,14 +466,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - FragmentId = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -1038,7 +1010,7 @@ public void Read_WithAvroSchemaPayload_NoErrors() // Assert diagnostics.Errors.Should().HaveCount(0); - result.Channels.First().Value.Publish.Message.First().Payload.As().TryGetAs(out var record).Should().BeTrue(); + result.Channels.First().Value.Publish.Message.First().Payload.As().TryGetAs(out var record).Should().BeTrue(); record.Name.Should().Be("UserSignedUp"); } @@ -1091,7 +1063,7 @@ public void Read_WithJsonSchemaReference_NoErrors() // Assert diagnostics.Errors.Should().HaveCount(0); result.Channels.First().Value.Publish.Message.First().Title.Should().Be("Message for schema validation testing that is a json object"); - result.Channels.First().Value.Publish.Message.First().Payload.As().Properties.Should().HaveCount(1); + result.Channels.First().Value.Publish.Message.First().Payload.As().Properties.Should().HaveCount(1); } [Test] diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index a229b861..52313aa0 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -699,7 +699,7 @@ public void Read_WithBasicPlusSecurityRequirementsDeserializes() implicit: authorizationUrl: https://example.com/api/oauth/dialog scopes: - write:pets: modify pets in your account + write:pets: modify pets in your account read:pets: read your pets """; var reader = new AsyncApiStringReader(); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index f300f97d..1a7aee2f 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -41,7 +41,7 @@ public void AsyncApiMessage_WithNoType_DeserializesToDefault() // Assert diagnostic.Errors.Should().BeEmpty(); - message.Payload.As().Properties.First().Value.Enum.Should().HaveCount(2); + message.Payload.As().Properties.First().Value.Enum.Should().HaveCount(2); } [Test] @@ -104,7 +104,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() """; var message = new AsyncApiMessage(); - message.Payload = new AsyncApiJsonSchemaPayload() + message.Payload = new AsyncApiJsonSchema() { Properties = new Dictionary() { @@ -145,7 +145,7 @@ public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() var message = new AsyncApiMessage(); message.SchemaFormat = "application/vnd.aai.asyncapi+json;version=2.6.0"; - message.Payload = new AsyncApiJsonSchemaPayload() + message.Payload = new AsyncApiJsonSchema() { Properties = new Dictionary() { @@ -205,7 +205,7 @@ public void AsyncApiMessage_WithAvroSchemaFormat_Serializes() }, }, }; - message.Payload = new AsyncApiAvroSchemaPayload(schema); + message.Payload = schema; // Act var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); @@ -232,10 +232,12 @@ public void AsyncApiMessage_WithAvroAsReference_Deserializes() var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); // Assert + //TODO: reimplement this test. + //var payloadReference = deserializedMessage.Payload as AsyncApiAvroSchemaReference; //deserializedMessage.Payload.UnresolvedReference.Should().BeTrue(); - deserializedMessage.Payload.Reference.Should().NotBeNull(); - deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); - deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); + //deserializedMessage.Payload.Reference.Should().NotBeNull(); + //deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); + //deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); } @@ -327,7 +329,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() }), }, }, - Payload = new AsyncApiJsonSchemaPayload() + Payload = new AsyncApiJsonSchema() { Properties = new Dictionary { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 6b76b18d..44c37574 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -157,7 +157,7 @@ public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); payload.UnresolvedReference.Should().BeTrue(); var reference = payload.Reference; @@ -186,7 +186,7 @@ public void AsyncApiReference_WithFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); payload.UnresolvedReference.Should().BeTrue(); var reference = payload.Reference; @@ -215,11 +215,11 @@ public void AsyncApiReference_WithInternalComponentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().BeNull(); reference.Type.Should().Be(ReferenceType.Schema); - reference.FragmentId.Should().Be("test"); + reference.FragmentId.Should().Be("/components/schemas/test"); reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeFalse(); @@ -243,7 +243,7 @@ public void AsyncApiReference_WithExternalFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("./myjsonfile.json"); reference.FragmentId.Should().Be("/fragment"); @@ -270,7 +270,7 @@ public void AsyncApiReference_WithExternalComponentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("./someotherdocument.json"); reference.Type.Should().Be(ReferenceType.Schema); @@ -373,7 +373,7 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/json.json"); reference.FragmentId.Should().BeNull(); @@ -411,7 +411,7 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var doc = reader.Read(yaml, out var diagnostic); var message = doc.Channels["workspace"].Publish.Message.First(); message.Name.Should().Be("Test"); - var payload = message.Payload.As(); + var payload = message.Payload.As(); payload.Properties.Count.Should().Be(1); } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 945e6753..af2badf8 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -220,12 +220,6 @@ public class AsyncApiSchema_Should : TestBase { Url = new Uri("http://example.com/externalDocs"), }, - - Reference = new AsyncApiReference - { - Type = ReferenceType.Schema, - FragmentId = "schemaObject1", - }, }; public static AsyncApiJsonSchema AdvancedSchemaWithRequiredPropertiesObject = new AsyncApiJsonSchema @@ -420,14 +414,14 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl { new AsyncApiMessage { - Payload = new AsyncApiJsonSchemaPayload + Payload = new AsyncApiJsonSchema { Type = SchemaType.Object, Required = new HashSet { "testB" }, Properties = new Dictionary { - { "testC", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testC" } } }, - { "testB", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testB" } } }, + { "testC", new AsyncApiJsonSchemaReference("#/components/schemas/testC") }, + { "testB", new AsyncApiJsonSchemaReference("#/components/schemas/testB") }, }, }, }, @@ -440,7 +434,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl Type = SchemaType.Object, Properties = new Dictionary { - { "testD", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, FragmentId = "testD" } } }, + { "testD", new AsyncApiJsonSchemaReference("#/components/schemas/testD") }, }, }) .WithComponent("testB", new AsyncApiJsonSchema() { Description = "test", Type = SchemaType.Boolean }) diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 4dcce166..88cb7c87 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -28,7 +28,7 @@ public void Serialize_WithDefaultNull_SetJsonNull() default: null """; - var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); var reserialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0); reserialized.Should().Contain("default\": null"); } @@ -72,7 +72,7 @@ public void Deserialize_WithMetadata_CreatesMetadata() } } """; - var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); model.Metadata.Should().HaveCount(1); var reserialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0); @@ -218,7 +218,7 @@ public void SerializeV2_SerializesCorrectly() Name = "contact", Type = new AvroUnion { - Types = new List + Types = new List { AvroPrimitiveType.Null, new AvroRecord @@ -294,7 +294,7 @@ public void SerializeV2_WithLogicalTypes_SerializesCorrectly() """; // Act - var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); var serialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0); // Assert model.As().Items.As().Types.Should().HaveCount(8); @@ -438,7 +438,7 @@ public void ReadFragment_DeserializesCorrectly() Name = "contact", Type = new AvroUnion { - Types = new List + Types = new List { AvroPrimitiveType.Null, new AvroRecord @@ -458,7 +458,7 @@ public void ReadFragment_DeserializesCorrectly() }; // Act - var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic); // Assert actual.Should() From 06abb47adbf6e990e46c65d133dabe2e1c4a953b Mon Sep 17 00:00:00 2001 From: VisualBean Date: Wed, 29 Jan 2025 12:43:38 +0100 Subject: [PATCH 13/20] remote notion of HostDocument in favour of workspace --- .../AsyncApiJsonDocumentReader.cs | 4 ++-- .../AsyncApiReferenceHostDocumentResolver.cs | 8 ++++---- .../Services/AsyncApiRemoteReferenceCollector.cs | 10 +++++----- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 6 ++++++ src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 2 +- .../Models/References/AsyncApiChannelReference.cs | 4 ++-- .../References/AsyncApiCorrelationIdReference.cs | 4 ++-- .../Models/References/AsyncApiJsonSchemaReference.cs | 4 ++-- .../Models/References/AsyncApiMessageReference.cs | 4 ++-- .../Models/References/AsyncApiMessageTraitReference.cs | 4 ++-- .../References/AsyncApiOperationTraitReference.cs | 4 ++-- .../Models/References/AsyncApiParameterReference.cs | 4 ++-- .../References/AsyncApiSecuritySchemeReference.cs | 4 ++-- .../Models/References/AsyncApiServerReference.cs | 4 ++-- .../References/AsyncApiServerVariableReference.cs | 4 ++-- src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs | 2 +- src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs | 2 +- .../Models/AsyncApiReference_Should.cs | 2 -- 19 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 73bdafc6..83d69ab6 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -197,7 +197,7 @@ private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo { var reader = new AsyncApiStringReader(this.settings); - var resolver = new AsyncApiReferenceHostDocumentResolver(document); + var resolver = new AsyncApiReferenceHostDocumentResolver(this.context.Workspace); var walker = new AsyncApiWalker(resolver); walker.Walk(document); @@ -210,7 +210,7 @@ private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDo private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable, AsyncApiDocument hostDocument) { var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); - var collector = new AsyncApiRemoteReferenceCollector(hostDocument); + var collector = new AsyncApiRemoteReferenceCollector(this.context.Workspace); var walker = new AsyncApiWalker(collector); walker.Walk(serializable); diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs index c170207b..50bad0a3 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs @@ -9,13 +9,13 @@ namespace LEGO.AsyncAPI.Readers internal class AsyncApiReferenceHostDocumentResolver : AsyncApiVisitorBase { - private AsyncApiDocument currentDocument; + private AsyncApiWorkspace workspace; private List errors = new List(); public AsyncApiReferenceHostDocumentResolver( - AsyncApiDocument currentDocument) + AsyncApiWorkspace workspace) { - this.currentDocument = currentDocument; + this.workspace = workspace; } public IEnumerable Errors @@ -30,7 +30,7 @@ public override void Visit(IAsyncApiReferenceable referenceable) { if (referenceable.Reference != null) { - referenceable.Reference.HostDocument = this.currentDocument; + referenceable.Reference.Workspace = this.workspace; } } } diff --git a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs index 1d33251e..79e732a1 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -10,12 +10,12 @@ namespace LEGO.AsyncAPI.Readers.Services internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase { private readonly List references = new(); - private AsyncApiDocument currentDocument; + private AsyncApiWorkspace workspace; public AsyncApiRemoteReferenceCollector( - AsyncApiDocument currentDocument) + AsyncApiWorkspace workspace) { - this.currentDocument = currentDocument; + this.workspace = workspace; } /// /// List of all external references collected from AsyncApiDocument. @@ -36,9 +36,9 @@ public override void Visit(IAsyncApiReferenceable referenceable) { if (referenceable.Reference != null && referenceable.Reference.IsExternal) { - if (referenceable.Reference.HostDocument == null) + if (referenceable.Reference.Workspace == null) { - referenceable.Reference.HostDocument = this.currentDocument; + referenceable.Reference.Workspace = this.workspace; } this.references.Add(referenceable); diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index 9bcf8eeb..bb352ea8 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -161,6 +161,12 @@ public bool Contains(string location) return this.resolvedReferenceRegistry.ContainsKey(key) || this.artifactsRegistry.ContainsKey(key); } + public T ResolveReference(AsyncApiReference reference) + where T : class + { + return this.ResolveReference(reference.Reference); + } + public T ResolveReference(string location) where T : class { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 5aad6e40..adc797ef 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -74,8 +74,8 @@ public void SerializeV2(IAsyncApiWriter writer) } this.Workspace.RegisterComponents(this); + writer.Workspace = this.Workspace; - writer.RootDocument = this; writer.WriteStartObject(); // asyncApi diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 69e819ee..8964e1d3 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -105,7 +105,7 @@ public string ExternalResource /// /// Gets or sets the AsyncApiDocument that is hosting the AsyncApiReference instance. This is used to enable dereferencing the reference. /// - public AsyncApiDocument HostDocument { get; set; } = null; + public AsyncApiWorkspace Workspace { get; set; } = null; /// /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment. diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs index 504dbf00..437c4e50 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs @@ -14,7 +14,7 @@ private AsyncApiChannel Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.Reference); return this.target; } } @@ -51,7 +51,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs index e423d8e3..89784131 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs @@ -17,7 +17,7 @@ private AsyncApiCorrelationId Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.Reference); return this.target; } } @@ -46,7 +46,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs index a3b7d729..a7211559 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs @@ -15,7 +15,7 @@ private AsyncApiJsonSchema Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -304,7 +304,7 @@ public override void SerializeV2(IAsyncApiWriter writer) return; } - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; // If Loop is detected then just Serialize as a reference. if (!settings.LoopDetector.PushLoop(this)) { diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs index 2e6dbdf2..05aa8ed8 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -17,7 +17,7 @@ private AsyncApiMessage Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.Reference); return this.target; } } @@ -72,7 +72,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs index 76cc38db..118ad4b9 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs @@ -17,7 +17,7 @@ private AsyncApiMessageTrait Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -68,7 +68,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs index 92bee6fe..36bb3bd9 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs @@ -17,7 +17,7 @@ private AsyncApiOperationTrait Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -54,7 +54,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs index 8ae13fc3..1c570ca0 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs @@ -17,7 +17,7 @@ private AsyncApiParameter Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -48,7 +48,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs index 75b8a189..7e8440ef 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -18,7 +18,7 @@ private AsyncApiSecurityScheme Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -59,7 +59,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs index 4927b3f2..1ef0d510 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -17,7 +17,7 @@ private AsyncApiServer Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -58,7 +58,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs index 82cb6ae0..c5bb968d 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs @@ -17,7 +17,7 @@ private AsyncApiServerVariable Target { get { - this.target ??= this.Reference.HostDocument?.ResolveReference(this.Reference); + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); return this.target; } } @@ -49,7 +49,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - this.Reference.HostDocument = writer.RootDocument; + this.Reference.Workspace = writer.Workspace; this.Target.SerializeV2(writer); } } diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs index aa123d7b..d6d6b750 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs @@ -68,7 +68,7 @@ public AsyncApiWriterBase(TextWriter textWriter, AsyncApiWriterSettings settings /// protected TextWriter Writer { get; } - public AsyncApiDocument RootDocument { get; set; } + public AsyncApiWorkspace Workspace { get; set; } = new AsyncApiWorkspace(); /// /// Write start object. diff --git a/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs b/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs index 7116a7c5..e85bb834 100644 --- a/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs +++ b/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Writers { public interface IAsyncApiWriter { - AsyncApiDocument RootDocument { get; set; } + AsyncApiWorkspace Workspace { get; set; } /// /// Write the start object. diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 44c37574..abfcef91 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -68,7 +68,6 @@ public void Reference() var reference = doc.Servers.First().Value as AsyncApiServerReference; reference.Reference.ExternalResource.Should().Be("https://github.com/test/test"); reference.Reference.FragmentId.Should().Be("whatever"); - reference.Reference.HostDocument.Should().Be(doc); reference.Reference.IsFragment.Should().BeTrue(); } @@ -136,7 +135,6 @@ public void ServerReference_WithComponentReference_ResolvesReference() var reference = doc.Servers.First().Value as AsyncApiServerReference; reference.Reference.ExternalResource.Should().BeNull(); reference.Reference.FragmentId.Should().Be("/components/servers/whatever"); - reference.Reference.HostDocument.Should().Be(doc); reference.Reference.IsFragment.Should().BeTrue(); reference.Url.Should().Be("wss://production.gigantic-server.com:443"); From 30ab5d95a9b270c8ca9b8d5e45783fd54ebe4c5a Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 30 Jan 2025 10:33:00 +0100 Subject: [PATCH 14/20] fix for logical types --- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 1 - src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 68 +------------------ .../Avro/LogicalTypes/AvroLogicalType.cs | 6 +- 3 files changed, 6 insertions(+), 69 deletions(-) diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index bb352ea8..3b6a2dd2 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -15,7 +15,6 @@ public class AsyncApiWorkspace public void RegisterComponents(AsyncApiDocument document) { - document.Workspace = this; if (document?.Components == null) { return; diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index adc797ef..5d90e7a8 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -12,8 +12,6 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable { - internal AsyncApiWorkspace Workspace { get; set; } = new AsyncApiWorkspace(); - /// /// REQUIRED. Specifies the AsyncAPI Specification version being used. /// @@ -73,8 +71,7 @@ public void SerializeV2(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - this.Workspace.RegisterComponents(this); - writer.Workspace = this.Workspace; + writer.Workspace.RegisterComponents(this); writer.WriteStartObject(); @@ -110,68 +107,5 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteEndObject(); } - - internal T? ResolveReference(AsyncApiReference reference) - where T : class, IAsyncApiSerializable - { - return this.Workspace.ResolveReference(reference.Reference); - } - - //internal IAsyncApiReferenceable ResolveReference(AsyncApiReference reference) - //{ - // if (reference == null) - // { - // return null; - // } - - // if (!reference.Type.HasValue) - // { - // throw new ArgumentException("Reference must have a type."); - // } - - // if (this.Components == null) - // { - // throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); - // } - - // try - // { - // switch (reference.Type) - // { - // case ReferenceType.Schema: - // return this.Components.Schemas[reference.Id]; - // case ReferenceType.Server: - // return this.Components.Servers[reference.Id]; - // case ReferenceType.Channel: - // return this.Components.Channels[reference.Id]; - // case ReferenceType.Message: - // return this.Components.Messages[reference.Id]; - // case ReferenceType.SecurityScheme: - // return this.Components.SecuritySchemes[reference.Id]; - // case ReferenceType.Parameter: - // return this.Components.Parameters[reference.Id]; - // case ReferenceType.CorrelationId: - // return this.Components.CorrelationIds[reference.Id]; - // case ReferenceType.OperationTrait: - // return this.Components.OperationTraits[reference.Id]; - // case ReferenceType.MessageTrait: - // return this.Components.MessageTraits[reference.Id]; - // case ReferenceType.ServerBindings: - // return this.Components.ServerBindings[reference.Id]; - // case ReferenceType.ChannelBindings: - // return this.Components.ChannelBindings[reference.Id]; - // case ReferenceType.OperationBindings: - // return this.Components.OperationBindings[reference.Id]; - // case ReferenceType.MessageBindings: - // return this.Components.MessageBindings[reference.Id]; - // default: - // throw new AsyncApiException("Invalid reference type."); - // } - // } - // catch (KeyNotFoundException) - // { - // throw new AsyncApiException(string.Format("Invalid reference Id: '{0}'", reference.Id)); - // } - //} } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs index bf834ebd..cb0f5b3c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs @@ -14,7 +14,11 @@ protected AvroLogicalType(AvroPrimitiveType type) public abstract LogicalType LogicalType { get; } - public override void SerializeV2Core(IAsyncApiWriter writer) + public virtual void SerializeV2Core(IAsyncApiWriter writer) + { + } + + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); From c785ee8ebd458f710c84ea4453079d28b97e19f5 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 30 Jan 2025 11:36:55 +0100 Subject: [PATCH 15/20] AvroReferences work. --- .../AsyncApiJsonDocumentReader.cs | 12 ++- .../V2/AsyncApiAvroSchemaDeserializer.cs | 6 +- .../Models/Avro/AsyncApiAvroSchema.cs | 36 ++++----- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 2 +- .../Models/Avro/AvroPrimitive.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 2 +- .../Models/Avro/LogicalTypes/AvroDuration.cs | 2 +- .../Models/MessagePayloadExtensions.cs | 13 +-- .../References/AsyncApiAvroSchemaReference.cs | 80 +++++++++++++++++++ .../Services/AsyncApiReferenceResolver.cs | 8 -- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 7 ++ .../Writers/AsyncApiWriterSettings.cs | 5 ++ .../Models/AsyncApiReference_Should.cs | 80 ++++++++++++++++++- 17 files changed, 210 insertions(+), 53 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs delete mode 100644 src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 83d69ab6..b9414202 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -295,8 +295,18 @@ private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiRe switch (reference.Reference.Type) { case ReferenceType.Schema: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + if (reference is AsyncApiJsonSchemaReference) + { + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + } + + if (reference is AsyncApiAvroSchemaReference) + { + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + } + break; + case ReferenceType.Server: result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 2a4d9b59..348005e0 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -236,11 +236,7 @@ public static AsyncApiAvroSchema LoadSchema(ParseNode node) if (pointer != null) { - return new AvroRecord - { - UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToAsyncApiReference(pointer, ReferenceType.Schema), - }; + return new AsyncApiAvroSchemaReference(pointer); } var isLogicalType = mapNode["logicalType"] != null; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs index d9200657..f19b0160 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs @@ -7,7 +7,7 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public abstract class AsyncApiAvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable, IAsyncApiMessagePayload + public abstract class AsyncApiAvroSchema : IAsyncApiSerializable, IAsyncApiMessagePayload { public abstract string Type { get; } @@ -16,31 +16,29 @@ public abstract class AsyncApiAvroSchema : IAsyncApiSerializable, IAsyncApiRefer /// public abstract IDictionary Metadata { get; set; } - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - public static implicit operator AsyncApiAvroSchema(AvroPrimitiveType type) { return new AvroPrimitive(type); } - public virtual void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } + public abstract void SerializeV2(IAsyncApiWriter writer); - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2Core(writer); + public virtual bool TryGetAs(out T result) +where T : AsyncApiAvroSchema + { + result = this as T; + return result != null; + } + public virtual bool Is() + where T : AsyncApiAvroSchema + { + return this is T; } - public abstract void SerializeV2Core(IAsyncApiWriter writer); + public virtual T As() + where T : AsyncApiAvroSchema + { + return this as T; + } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index b627bde7..d00e6f79 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -20,7 +20,7 @@ public class AvroArray : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index 6f984c39..ced40502 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -45,7 +45,7 @@ public class AvroEnum : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index f11b5164..e50ec714 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -36,7 +36,7 @@ public class AvroFixed : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 9752e453..ba762d69 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -17,7 +17,7 @@ public class AvroMap : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index c59a88ca..fe97023a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -20,7 +20,7 @@ public AvroPrimitive(AvroPrimitiveType type) this.Type = type.GetDisplayName(); } - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteValue(this.Type); if (this.Metadata.Any()) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index 4e2e27a1..bbb085a9 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -40,7 +40,7 @@ public class AvroRecord : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs index 9ef2dd37..df5832e1 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -20,7 +20,7 @@ public class AvroUnion : AsyncApiAvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartArray(); foreach (var type in this.Types) diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs index 35b457b0..44b5b8c6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs @@ -11,7 +11,7 @@ public class AvroDuration : AvroFixed public new int Size { get; } = 12; - public override void SerializeV2Core(IAsyncApiWriter writer) + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs index 8aa21a6d..069869b6 100644 --- a/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs +++ b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs @@ -15,11 +15,10 @@ public static bool TryGetAs(this IAsyncApiMessagePayload payload, out T resul return result != null; } - public static bool TryGetAs(this AsyncApiAvroSchema avroSchema, out T result) - where T : AsyncApiAvroSchema + public static T As(this IAsyncApiMessagePayload payload) + where T : class, IAsyncApiMessagePayload { - result = avroSchema as T; - return result != null; + return payload as T; } public static bool Is(this IAsyncApiMessagePayload payload) @@ -27,11 +26,5 @@ public static bool Is(this IAsyncApiMessagePayload payload) { return payload is T; } - - public static bool Is(this AsyncApiAvroSchema schema) - where T : AsyncApiAvroSchema - { - return schema is T; - } } } diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs new file mode 100644 index 00000000..31c281b4 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs @@ -0,0 +1,80 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiAvroSchemaReference : AsyncApiAvroSchema, IAsyncApiReferenceable + { + private AsyncApiAvroSchema target; + + private AsyncApiAvroSchema Target + { + get + { + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference); + return this.target; + } + } + + public AsyncApiAvroSchemaReference(string reference) + { + this.Reference = new AsyncApiReference(reference, ReferenceType.Schema); + } + + public override string Type => this.Target?.Type; + + public override IDictionary Metadata { get => this.Target?.Metadata; set => this.Target.Metadata = value; } + + public bool UnresolvedReference { get { return this.Target == null; } } + + public AsyncApiReference Reference { get; set; } + + public override T As() + { + if (this.Target == null) + { + return null; + } + return this.Target.As(); + } + + public override bool Is() + { + if (Target == null) + { + return false; + } + return this.Target.Is(); + } + + public override bool TryGetAs(out T result) + { + if (this.Target == null) + { + result = default; + return false; + } + return this.Target.TryGetAs(out result); + } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + + this.Target.SerializeV2(writer); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs deleted file mode 100644 index 2def4f53..00000000 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) The LEGO Group. All rights reserved. -namespace LEGO.AsyncAPI.Services -{ - /// - /// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects. - /// - -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 24a81a57..e1dd3160 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -343,6 +343,12 @@ internal void Walk(IAsyncApiMessagePayload payload) internal void Walk(AsyncApiAvroSchema schema) { + if (schema is AsyncApiAvroSchemaReference reference) + { + this.Walk(reference as IAsyncApiReferenceable); + return; + } + this.visitor.Visit(schema); } @@ -1079,6 +1085,7 @@ public void Walk(IAsyncApiElement element) case AsyncApiOperation e: this.Walk(e); break; case AsyncApiParameter e: this.Walk(e); break; case AsyncApiJsonSchema e: this.Walk(e); break; + case AsyncApiAvroSchema e: this.Walk(e); break; case AsyncApiSecurityRequirement e: this.Walk(e); break; case AsyncApiSecurityScheme e: this.Walk(e); break; case AsyncApiServer e: this.Walk(e); break; diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs index 1d866308..de7b2fb0 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs @@ -72,6 +72,11 @@ public ReferenceInlineSetting ReferenceInline /// True if it should be inlined otherwise false. public bool ShouldInlineReference(AsyncApiReference reference) { + if (reference.IsExternal) + { + return false; + } + return this.InlineLocalReferences; } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index abfcef91..fc951122 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -403,7 +403,7 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceLoader = new MockLoader(), + ExternalReferenceLoader = new MockJsonSchemaLoader(), }; var reader = new AsyncApiStringReader(settings); var doc = reader.Read(yaml, out var diagnostic); @@ -412,9 +412,85 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var payload = message.Payload.As(); payload.Properties.Count.Should().Be(1); } + + [Test] + public void AsyncApiReference_WithExternalAvroResource_DeserializesCorrectly() + { + var yaml = """ + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + message: + name: Test + title: Test message + summary: Test. + schemaFormat: application/vnd.apache.avro + contentType: application/cloudevents+json + payload: + $ref: "./some/path/to/external/payload.json" + """; + var settings = new AsyncApiReaderSettings + { + ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + ExternalReferenceLoader = new MockAvroSchemaLoader(), + }; + var reader = new AsyncApiStringReader(settings); + var doc = reader.Read(yaml, out var diagnostic); + var message = doc.Channels["workspace"].Publish.Message.First(); + var payload = message.Payload.As(); + payload.As().Name.Should().Be("thecodebuzz_schema"); + } } - public class MockLoader : IStreamLoader + public class MockAvroSchemaLoader : IStreamLoader + { + const string Payload = + """ + { + "type": "record", + "name": "thecodebuzz_schema", + "namespace": "thecodebuzz.avro", + "fields": [ + { + "name": "username", + "type": "string", + "doc": "Name of the user account on Thecodebuzz.com" + }, + { + "name": "email", + "type": "string", + "doc": "The email of the user logging message on the blog" + }, + { + "name": "timestamp", + "type": "long", + "doc": "time in seconds" + } + ], + "doc:": "A basic schema for storing thecodebuzz blogs messages" + } + """; + + public Stream Load(Uri uri) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(Payload); + writer.Flush(); + stream.Position = 0; + return stream; + } + + public Task LoadAsync(Uri uri) + { + return Task.FromResult(this.Load(uri)); + } + } + public class MockJsonSchemaLoader : IStreamLoader { const string Message = """ From fedf798a7e9e6c69ade17bd987443c4ef9de7c35 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 30 Jan 2025 11:46:27 +0100 Subject: [PATCH 16/20] fixed up some reference tests --- .../Sns/SnsOperationBinding.cs | 2 +- .../References/AsyncApiAvroSchemaReference.cs | 2 +- .../Models/AsyncApiMessage_Should.cs | 11 +- .../Models/AsyncApiReference_Should.cs | 135 +++++++++++------- 4 files changed, 92 insertions(+), 58 deletions(-) diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs index 350b7a59..6f2dbba9 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs @@ -59,7 +59,7 @@ public class SnsOperationBinding : OperationBinding private FixedFieldMap redrivePolicyFixedFields => new() { - { "deadLetterQueue", (a, n) => { a.DeadLetterQueue = n.ParseMapWithExtensions(identifierFixFields); } }, + { "deadLetterQueue", (a, n) => { a.DeadLetterQueue = n.ParseMapWithExtensions(this.identifierFixFields); } }, { "maxReceiveCount", (a, n) => { a.MaxReceiveCount = n.GetIntegerValue(); } }, }; diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs index 31c281b4..decfc0c6 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs @@ -44,7 +44,7 @@ public override T As() public override bool Is() { - if (Target == null) + if (this.Target == null) { return false; } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index 1a7aee2f..c2e7142e 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -232,12 +232,11 @@ public void AsyncApiMessage_WithAvroAsReference_Deserializes() var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); // Assert - //TODO: reimplement this test. - //var payloadReference = deserializedMessage.Payload as AsyncApiAvroSchemaReference; - //deserializedMessage.Payload.UnresolvedReference.Should().BeTrue(); - //deserializedMessage.Payload.Reference.Should().NotBeNull(); - //deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); - //deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); + var payloadReference = deserializedMessage.Payload as AsyncApiAvroSchemaReference; + payloadReference.UnresolvedReference.Should().BeTrue(); + payloadReference.Reference.Should().NotBeNull(); + payloadReference.Reference.IsExternal.Should().BeTrue(); + payloadReference.Reference.IsFragment.Should().BeTrue(); } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index fc951122..2e536b25 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -72,26 +72,50 @@ public void Reference() } [Test] - public void ResolveExternalReference() + public void ExternalFragmentReference_ResolvesFragtment() { + var externalJson = + """ + { + "servers": [ + { + "url": "wss://production.gigantic-server.com:443", + "protocol": "wss", + "protocolVersion": "1.0.0", + "description": "The production API server", + "variables": { + "username": { + "default": "demo", + "description": "This value is assigned by the service provider" + }, + "password": { + "default": "demo", + "description": "This value is assigned by the service provider" + } + } + } + ] + } + """; + var json = """ - { - "asyncapi": "2.6.0", - "info": { }, - "servers": { - "production": { - "$ref": "https://gist.githubusercontent.com/VisualBean/7dc9607d735122483e1bb7005ff3ad0e/raw/458729e4d56636ef3bb34762f4a5731ea5043bdf/servers.json#/servers/0" - } - } - } - """; - - var doc = new AsyncApiStringReader(new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences }).Read(json, out var diag); + { + "asyncapi": "2.6.0", + "info": { }, + "servers": { + "production": { + "$ref": "whatever/whatever.json#/servers/0" + } + } + } + """; + + var doc = new AsyncApiStringReader(new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, ExternalReferenceLoader = new MockStringLoader(externalJson) }).Read(json, out var diag); var reference = doc.Servers.First().Value as AsyncApiServerReference; - //reference.Reference.Id.Should().Be("whatever"); - //reference.Reference.HostDocument.Should().Be(doc); - //reference.Reference.IsFragment.Should().BeTrue(); + reference.Reference.FragmentId.Should().Be("/servers/0"); + reference.Reference.IsFragment.Should().BeTrue(); + reference.Url.Should().Be("wss://production.gigantic-server.com:443"); } @@ -416,8 +440,35 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect [Test] public void AsyncApiReference_WithExternalAvroResource_DeserializesCorrectly() { + var avroPayload = + """ + { + "type": "record", + "name": "thecodebuzz_schema", + "namespace": "thecodebuzz.avro", + "fields": [ + { + "name": "username", + "type": "string", + "doc": "Name of the user account on Thecodebuzz.com" + }, + { + "name": "email", + "type": "string", + "doc": "The email of the user logging message on the blog" + }, + { + "name": "timestamp", + "type": "long", + "doc": "time in seconds" + } + ], + "doc:": "A basic schema for storing thecodebuzz blogs messages" + } + """; + var yaml = """ - asyncapi: 2.3.0 + asyncapi: 2.6.0 info: title: test version: 1.0.0 @@ -425,61 +476,44 @@ public void AsyncApiReference_WithExternalAvroResource_DeserializesCorrectly() workspace: publish: message: + payload: + $ref: ./some/path/to/external/payload.json + schemaFormat: application/vnd.apache.avro name: Test title: Test message summary: Test. - schemaFormat: application/vnd.apache.avro - contentType: application/cloudevents+json - payload: - $ref: "./some/path/to/external/payload.json" """; var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceLoader = new MockAvroSchemaLoader(), + ExternalReferenceLoader = new MockStringLoader(avroPayload), }; var reader = new AsyncApiStringReader(settings); var doc = reader.Read(yaml, out var diagnostic); var message = doc.Channels["workspace"].Publish.Message.First(); var payload = message.Payload.As(); payload.As().Name.Should().Be("thecodebuzz_schema"); + + doc.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0).Should() + .BePlatformAgnosticEquivalentTo(yaml); + } } - public class MockAvroSchemaLoader : IStreamLoader + public class MockStringLoader : IStreamLoader { - const string Payload = - """ - { - "type": "record", - "name": "thecodebuzz_schema", - "namespace": "thecodebuzz.avro", - "fields": [ - { - "name": "username", - "type": "string", - "doc": "Name of the user account on Thecodebuzz.com" - }, - { - "name": "email", - "type": "string", - "doc": "The email of the user logging message on the blog" - }, - { - "name": "timestamp", - "type": "long", - "doc": "time in seconds" - } - ], - "doc:": "A basic schema for storing thecodebuzz blogs messages" - } - """; + public MockStringLoader(string input) + { + this.input = input; + } + + private readonly string input; public Stream Load(Uri uri) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.Write(Payload); + writer.Write(this.input); writer.Flush(); stream.Position = 0; return stream; @@ -490,6 +524,7 @@ public Task LoadAsync(Uri uri) return Task.FromResult(this.Load(uri)); } } + public class MockJsonSchemaLoader : IStreamLoader { const string Message = From 6816cd12cfa5c6c349e5102b82301a8ce2130e5a Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 30 Jan 2025 13:35:01 +0100 Subject: [PATCH 17/20] binding references done --- .../V2/AsyncApiChannelBindingDeserializer.cs | 2 +- .../V2/AsyncApiComponentsDeserializer.cs | 8 +- .../V2/AsyncApiMessageBindingDeserializer.cs | 6 +- .../AsyncApiOperationBindingDeserializer.cs | 6 +- .../V2/AsyncApiServerBindingDeserializer.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs | 11 -- .../Models/AsyncApiBindings{TBinding}.cs | 104 ++++++++++---- .../Models/AsyncApiComponents.cs | 24 ++-- .../AsyncApiBindingsReference{TBinding}.cs | 135 ++++++++++++++++++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 12 +- .../AsyncApiDocumentV2Tests.cs | 57 +++++--- .../AsyncApiReaderTests.cs | 26 ++++ .../Models/AsyncApiReference_Should.cs | 1 - 13 files changed, 312 insertions(+), 82 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiBindingsReference{TBinding}.cs diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelBindingDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelBindingDeserializer.cs index 1aab9e85..aa91b942 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelBindingDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelBindingDeserializer.cs @@ -15,7 +15,7 @@ internal static AsyncApiBindings LoadChannelBindings(ParseNode var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject>(ReferenceType.ChannelBindings, pointer); + return new AsyncApiBindingsReference(pointer); } var channelBindings = new AsyncApiBindings(); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index 1e1b44f9..75c50873 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -19,10 +19,10 @@ internal static partial class AsyncApiV2Deserializer { "correlationIds", (a, n) => a.CorrelationIds = n.CreateMap(LoadCorrelationId) }, { "operationTraits", (a, n) => a.OperationTraits = n.CreateMap(LoadOperationTrait) }, { "messageTraits", (a, n) => a.MessageTraits = n.CreateMap(LoadMessageTrait) }, - { "serverBindings", (a, n) => a.ServerBindings = n.CreateMapWithReference(ReferenceType.ServerBindings, LoadServerBindings) }, - { "channelBindings", (a, n) => a.ChannelBindings = n.CreateMapWithReference(ReferenceType.ChannelBindings, LoadChannelBindings) }, - { "operationBindings", (a, n) => a.OperationBindings = n.CreateMapWithReference(ReferenceType.OperationBindings, LoadOperationBindings) }, - { "messageBindings", (a, n) => a.MessageBindings = n.CreateMapWithReference(ReferenceType.MessageBindings, LoadMessageBindings) }, + { "serverBindings", (a, n) => a.ServerBindings = n.CreateMap(LoadServerBindings) }, + { "channelBindings", (a, n) => a.ChannelBindings = n.CreateMap(LoadChannelBindings) }, + { "operationBindings", (a, n) => a.OperationBindings = n.CreateMap(LoadOperationBindings) }, + { "messageBindings", (a, n) => a.MessageBindings = n.CreateMap(LoadMessageBindings) }, }; private static PatternFieldMap componentsPatternFields = diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageBindingDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageBindingDeserializer.cs index 9a180e84..eb58ca1f 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageBindingDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageBindingDeserializer.cs @@ -12,9 +12,13 @@ internal static partial class AsyncApiV2Deserializer internal static AsyncApiBindings LoadMessageBindings(ParseNode node) { var mapNode = node.CheckMapNode("messageBindings"); + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + return new AsyncApiBindingsReference(pointer); + } var messageBindings = new AsyncApiBindings(); - foreach (var property in mapNode) { var messageBinding = LoadMessageBinding(property); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationBindingDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationBindingDeserializer.cs index 4d4eb85f..0fb5db0a 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationBindingDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiOperationBindingDeserializer.cs @@ -12,9 +12,13 @@ internal static partial class AsyncApiV2Deserializer internal static AsyncApiBindings LoadOperationBindings(ParseNode node) { var mapNode = node.CheckMapNode("operationBindings"); + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + return new AsyncApiBindingsReference(pointer); + } var operationBindings = new AsyncApiBindings(); - foreach (var property in mapNode) { var operationBinding = LoadOperationBinding(property); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerBindingDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerBindingDeserializer.cs index 44c821d3..18a8ea84 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerBindingDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiServerBindingDeserializer.cs @@ -15,7 +15,7 @@ internal static AsyncApiBindings LoadServerBindings(ParseNode no var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject>(ReferenceType.ServerBindings, pointer); + return new AsyncApiBindingsReference(pointer); } var serverBindings = new AsyncApiBindings(); diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs b/src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs index 4034b4e9..a2c8303a 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Bindings { using System; using System.Collections.Generic; - using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -12,10 +11,6 @@ public abstract class AsyncApiBinding : IBinding { public abstract string BindingKey { get; } - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - public IDictionary Extensions { get; set; } = new Dictionary(); public string BindingVersion { get; set; } @@ -27,12 +22,6 @@ public void SerializeV2(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - this.SerializeProperties(writer); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs b/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs index f11858aa..260629ae 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs @@ -3,39 +3,17 @@ namespace LEGO.AsyncAPI.Models { using System; + using System.Collections; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public class AsyncApiBindings : Dictionary, IAsyncApiReferenceable + public class AsyncApiBindings : IDictionary, IAsyncApiSerializable where TBinding : IBinding { - public bool UnresolvedReference { get; set; } + private Dictionary inner = new Dictionary(); - public AsyncApiReference Reference { get; set; } - - public void Add(TBinding binding) - { - this[binding.BindingKey] = binding; - } - - public void SerializeV2(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) - { - this.Reference.SerializeV2(writer); - return; - } - - this.SerializeV2WithoutReference(writer); - } - - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + public virtual void SerializeV2(IAsyncApiWriter writer) { if (writer is null) { @@ -56,5 +34,79 @@ public void SerializeV2WithoutReference(IAsyncApiWriter writer) writer.WriteEndObject(); } + + public virtual void Add(TBinding binding) + { + this[binding.BindingKey] = binding; + } + + public virtual TBinding this[string key] + { + get => inner[key]; + set => inner[key] = value; + } + + public virtual ICollection Keys => inner.Keys; + + public virtual ICollection Values => inner.Values; + + public virtual int Count => inner.Count; + + public virtual bool IsReadOnly => ((IDictionary)inner).IsReadOnly; + + public virtual void Add(string key, TBinding value) + { + inner.Add(key, value); + } + + public virtual bool ContainsKey(string key) + { + return inner.ContainsKey(key); + } + + public virtual bool Remove(string key) + { + return inner.Remove(key); + } + + public virtual bool TryGetValue(string key, out TBinding value) + { + return inner.TryGetValue(key, out value); + } + + public virtual void Add(KeyValuePair item) + { + ((IDictionary)inner).Add(item); + } + + public virtual void Clear() + { + inner.Clear(); + } + + public virtual bool Contains(KeyValuePair item) + { + return ((IDictionary)inner).Contains(item); + } + + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)inner).CopyTo(array, arrayIndex); + } + + public virtual bool Remove(KeyValuePair item) + { + return ((IDictionary)inner).Remove(item); + } + + public virtual IEnumerator> GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } } } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs index bd9e5aef..0d00a9d9 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -290,11 +290,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.ServerBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.ServerBindings && - component.Reference.FragmentId == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -308,11 +306,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.ChannelBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.ChannelBindings && - component.Reference.FragmentId == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -326,11 +322,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.OperationBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.OperationBindings && - component.Reference.FragmentId == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -344,11 +338,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.MessageBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.MessageBindings && - component.Reference.FragmentId == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiBindingsReference{TBinding}.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiBindingsReference{TBinding}.cs new file mode 100644 index 00000000..378d67d0 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiBindingsReference{TBinding}.cs @@ -0,0 +1,135 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiBindingsReference : AsyncApiBindings, IAsyncApiReferenceable + where TBinding : IBinding + { + public bool UnresolvedReference { get => this.target == null; } + + public AsyncApiReference Reference { get; set; } + + private AsyncApiBindings target; + + private AsyncApiBindings Target + { + get + { + this.target ??= this.Reference.Workspace?.ResolveReference>(this.Reference); + return this.target; + } + } + + public override void Add(TBinding binding) + { + this.Target.Add(binding); + } + + public override ICollection Keys => this.Target.Keys; + + public override ICollection Values => this.Target.Values; + + public override int Count => this.Target.Count; + + public override bool IsReadOnly => this.Target.IsReadOnly; + + public AsyncApiBindingsReference(string reference) + { + ReferenceType type = ReferenceType.None; + if (typeof(TBinding) == typeof(IServerBinding)) + { + type = ReferenceType.ServerBindings; + } + if (typeof(TBinding) == typeof(IMessageBinding)) + { + type = ReferenceType.MessageBindings; + } + if (typeof(TBinding) == typeof(IOperationBinding)) + { + type = ReferenceType.OperationBindings; + } + if (typeof(TBinding) == typeof(IChannelBinding)) + { + type = ReferenceType.ChannelBindings; + } + + if (type == ReferenceType.None) + { + throw new NotImplementedException($"Binding type '{typeof(TBinding)}' not supported."); + } + + this.Reference = new AsyncApiReference(reference, type); + } + + public override void SerializeV2(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) + { + this.Reference.SerializeV2(writer); + return; + } + + this.Target.SerializeV2(writer); + } + + public override void Add(string key, TBinding value) + { + this.Target.Add(key, value); + } + + public override bool ContainsKey(string key) + { + return this.Target.ContainsKey(key); + } + + public override bool Remove(string key) + { + return this.Target.Remove(key); + } + + public override bool TryGetValue(string key, out TBinding value) + { + return this.Target.TryGetValue(key, out value); + } + + public override void Add(KeyValuePair item) + { + this.Target.Add(item); + } + + public override void Clear() + { + this.Target.Clear(); + } + + public override bool Contains(KeyValuePair item) + { + return this.Target.Contains(item); + } + + public override void CopyTo(KeyValuePair[] array, int arrayIndex) + { + this.Target.CopyTo(array, arrayIndex); + } + + public override bool Remove(KeyValuePair item) + { + return this.Target.Remove(item); + } + + public override IEnumerator> GetEnumerator() + { + return this.Target.GetEnumerator(); + } + } +} diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index e1dd3160..2ee2226b 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -624,8 +624,9 @@ internal void Walk(AsyncApiMessageTrait trait, bool isComponent = false) internal void Walk(AsyncApiBindings serverBindings, bool isComponent = false) { - if (serverBindings == null || this.ProcessAsReference(serverBindings, isComponent)) + if (serverBindings is AsyncApiBindingsReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -653,8 +654,9 @@ internal void Walk(IServerBinding binding) internal void Walk(AsyncApiBindings channelBindings, bool isComponent = false) { - if (channelBindings == null || this.ProcessAsReference(channelBindings, isComponent)) + if (channelBindings is AsyncApiBindingsReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -682,8 +684,9 @@ internal void Walk(IChannelBinding binding) internal void Walk(AsyncApiBindings operationBindings, bool isComponent = false) { - if (operationBindings == null || this.ProcessAsReference(operationBindings, isComponent)) + if (operationBindings is AsyncApiBindingsReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -711,8 +714,9 @@ internal void Walk(IOperationBinding binding) internal void Walk(AsyncApiBindings messageBindings, bool isComponent = false) { - if (messageBindings == null || this.ProcessAsReference(messageBindings, isComponent)) + if (messageBindings is AsyncApiBindingsReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 53434e55..9b863969 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1069,6 +1069,38 @@ public void Read_WithJsonSchemaReference_NoErrors() [Test] public void Serialize_WithBindingReferences_SerializesDeserializes() { + var expected = + """ + asyncapi: 2.6.0 + info: + description: test description + servers: + production: + url: example.com + protocol: pulsar+ssl + description: test description + bindings: + $ref: '#/components/serverBindings/bindings' + channels: + testChannel: + $ref: '#/components/channels/otherchannel' + components: + channels: + otherchannel: + publish: + description: test + bindings: + $ref: '#/components/channelBindings/bindings' + serverBindings: + bindings: + pulsar: + tenant: staging + channelBindings: + bindings: + pulsar: + namespace: users + persistence: persistent + """; var doc = new AsyncApiDocument(); doc.Info = new AsyncApiInfo() { @@ -1079,14 +1111,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() Description = "test description", Protocol = "pulsar+ssl", Url = "example.com", - Bindings = new AsyncApiBindings() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.ServerBindings, - FragmentId = "bindings", - }, - }, + Bindings = new AsyncApiBindingsReference("#/components/serverBindings/bindings") }); doc.Components = new AsyncApiComponents() { @@ -1099,14 +1124,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() { Description = "test", }, - Bindings = new AsyncApiBindings() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.ChannelBindings, - FragmentId = "bindings", - }, - }, + Bindings = new AsyncApiBindingsReference("#/components/channelBindings/bindings") } }, }, @@ -1140,11 +1158,18 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() "testChannel", new AsyncApiChannelReference("#/components/channels/otherchannel")); var actual = doc.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); + actual.Should().BePlatformAgnosticEquivalentTo(expected); var settings = new AsyncApiReaderSettings(); settings.Bindings = BindingsCollection.Pulsar; var reader = new AsyncApiStringReader(settings); var deserialized = reader.Read(actual, out var diagnostic); + var serverBindings = deserialized.Servers.First().Value.Bindings; + serverBindings.TryGetValue(out var binding); + binding.Tenant.Should().Be("staging"); + + var reserialized = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + reserialized.Should().BePlatformAgnosticEquivalentTo(expected); } [Test] diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index 52313aa0..881b7826 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -22,6 +22,32 @@ public void Read_WithMissingEverything_DeserializesWithErrors() var doc = reader.Read(yaml, out var diagnostic); } + [Test] + public void Read_WithComponentBindings_Deserializes() + { + var yaml = @" + asyncapi: 2.6.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + bindings: + http: + type: response + message: + $ref: '#/components/messages/WorkspaceEventPayload' + components: + messages: + WorkspaceEventPayload: + schemaFormat: application/schema+yaml;version=draft-07 + "; + var reader = new AsyncApiStringReader(); + var doc = reader.Read(yaml, out var diagnostic); + Assert.AreEqual("application/schema+yaml;version=draft-07", doc.Components.Messages["WorkspaceEventPayload"].SchemaFormat); + } + [Test] public void Read_WithExtensionParser_Parses() { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 2e536b25..ab8a4f52 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -118,7 +118,6 @@ public void ExternalFragmentReference_ResolvesFragtment() reference.Url.Should().Be("wss://production.gigantic-server.com:443"); } - [Test] public void ServerReference_WithComponentReference_ResolvesReference() { From 661bfdb8a674286c71b6c1e7ee95242b3e491c51 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 31 Jan 2025 10:58:43 +0100 Subject: [PATCH 18/20] register root document for document level fragment resolution --- .../AsyncApiJsonDocumentReader.cs | 117 +++++++++--------- .../AsyncApiReferenceHostDocumentResolver.cs | 13 +- src/LEGO.AsyncAPI.Readers/ParsingContext.cs | 19 ++- .../AsyncApiRemoteReferenceCollector.cs | 7 +- .../V2/AsyncApiV2VersionService.cs | 5 + src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 10 +- .../Models/AsyncApiReference_Should.cs | 23 ++++ 7 files changed, 111 insertions(+), 83 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index b9414202..3267d6ee 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -172,45 +172,14 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi return (T)element; } - private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private void ResolveReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable) { - switch (this.settings.ReferenceResolution) + if (this.settings.ReferenceResolution == ReferenceResolutionSetting.DoNotResolveReferences) { - case ReferenceResolutionSetting.ResolveAllReferences: - this.ResolveAllReferences(diagnostic, document); - break; - case ReferenceResolutionSetting.ResolveInternalReferences: - this.ResolveInternalReferences(diagnostic, document); - break; - case ReferenceResolutionSetting.DoNotResolveReferences: - break; - } - } - - private void ResolveAllReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) - { - this.ResolveInternalReferences(diagnostic, document); - this.ResolveExternalReferences(diagnostic, document, document); - } - - private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) - { - var reader = new AsyncApiStringReader(this.settings); - - var resolver = new AsyncApiReferenceHostDocumentResolver(this.context.Workspace); - var walker = new AsyncApiWalker(resolver); - walker.Walk(document); - - foreach (var item in resolver.Errors) - { - diagnostic.Errors.Add(item); + return; } - } - private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable, AsyncApiDocument hostDocument) - { - var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); - var collector = new AsyncApiRemoteReferenceCollector(this.context.Workspace); + var collector = new AsyncApiReferenceCollector(this.context.Workspace); var walker = new AsyncApiWalker(collector); walker.Walk(serializable); @@ -221,33 +190,60 @@ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, IAsyncApiS continue; } - try + IAsyncApiSerializable component = null; + if (reference.Reference.IsExternal) { - Stream stream; - if (this.context.Workspace.Contains(reference.Reference.ExternalResource)) + if (this.settings.ReferenceResolution != ReferenceResolutionSetting.ResolveAllReferences) { - stream = this.context.Workspace.ResolveReference(reference.Reference.ExternalResource); - } - else - { - stream = loader.Load(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute)); - this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream); - } - - var component = this.ResolveStreamReferences(stream, reference, diagnostic); - if (component == null) - { - diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); continue; } - this.context.Workspace.RegisterComponent(reference.Reference.Reference, component); - this.ResolveExternalReferences(diagnostic, component, hostDocument); + component = this.ResolveExternalReference(diagnostic, reference); + } + else + { + var stream = this.context.Workspace.ResolveReference(string.Empty); // get whole document. + component = this.ResolveStreamReference(stream, reference, diagnostic); + } + + if (component == null) + { + diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); + continue; + } + + this.context.Workspace.RegisterComponent(reference.Reference.Reference, component); + this.ResolveReferences(diagnostic, component); + } + } + + private IAsyncApiSerializable ResolveExternalReference(AsyncApiDiagnostic diagnostic, IAsyncApiReferenceable reference) + { + if (reference is null) + { + throw new ArgumentNullException(nameof(reference)); + } + + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); + try + { + Stream stream; + if (this.context.Workspace.Contains(reference.Reference.ExternalResource)) + { + stream = this.context.Workspace.ResolveReference(reference.Reference.ExternalResource); } - catch (AsyncApiException ex) + else { - diagnostic.Errors.Add(new AsyncApiError(ex)); + stream = loader.Load(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute)); + this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream); } + + return this.ResolveStreamReference(stream, reference, diagnostic); + } + catch (AsyncApiException ex) + { + diagnostic.Errors.Add(new AsyncApiError(ex)); + return null; } } @@ -264,7 +260,7 @@ private JsonNode ReadToJson(Stream stream) return default; } - private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) + private IAsyncApiSerializable ResolveStreamReference(Stream stream, IAsyncApiReferenceable reference, AsyncApiDiagnostic diagnostic) { JsonNode json = null; try @@ -274,6 +270,7 @@ private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiRe catch { diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference: '{reference.Reference.Reference}'")); + return null; } if (reference.Reference.IsFragment) @@ -329,19 +326,19 @@ private IAsyncApiSerializable ResolveStreamReferences(Stream stream, IAsyncApiRe result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.MessageTrait: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.ServerBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.ChannelBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.OperationBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; case ReferenceType.MessageBindings: - result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); break; default: diagnostic.Errors.Add(new AsyncApiError(reference.Reference.Reference, "Could not resolve reference.")); diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs index 50bad0a3..362b7011 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs @@ -7,25 +7,16 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Services; - internal class AsyncApiReferenceHostDocumentResolver : AsyncApiVisitorBase + internal class AsyncApiReferenceWorkspaceResolver : AsyncApiVisitorBase { private AsyncApiWorkspace workspace; - private List errors = new List(); - public AsyncApiReferenceHostDocumentResolver( + public AsyncApiReferenceWorkspaceResolver( AsyncApiWorkspace workspace) { this.workspace = workspace; } - public IEnumerable Errors - { - get - { - return this.errors; - } - } - public override void Visit(IAsyncApiReferenceable referenceable) { if (referenceable.Reference != null) diff --git a/src/LEGO.AsyncAPI.Readers/ParsingContext.cs b/src/LEGO.AsyncAPI.Readers/ParsingContext.cs index 22588edf..5fb0c2b3 100644 --- a/src/LEGO.AsyncAPI.Readers/ParsingContext.cs +++ b/src/LEGO.AsyncAPI.Readers/ParsingContext.cs @@ -4,7 +4,9 @@ namespace LEGO.AsyncAPI.Readers { using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Text.Json; using System.Text.Json.Nodes; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; @@ -81,7 +83,10 @@ internal AsyncApiDocument Parse(JsonNode jsonNode) case string version when version.StartsWith("2"): this.VersionService = new AsyncApiV2VersionService(this.Diagnostic); doc = this.VersionService.LoadDocument(this.RootNode); - this.Workspace.RegisterComponents(doc); + + // Register components + this.Workspace.RegisterComponents(doc); // pre-register components. + this.Workspace.RegisterComponent(string.Empty, this.ParseToStream(jsonNode)); // register root document. this.Diagnostic.SpecificationVersion = AsyncApiVersion.AsyncApi2_0; break; @@ -92,6 +97,18 @@ internal AsyncApiDocument Parse(JsonNode jsonNode) return doc; } + private Stream ParseToStream(JsonNode node) + { + var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + node.WriteTo(writer); + } + + stream.Position = 0; + return stream; + } + internal T ParseFragment(JsonNode jsonNode, AsyncApiVersion version) where T : IAsyncApiElement { diff --git a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs index 79e732a1..4094fc6a 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -3,16 +3,15 @@ namespace LEGO.AsyncAPI.Readers.Services { using System.Collections.Generic; - using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Services; - internal class AsyncApiRemoteReferenceCollector : AsyncApiVisitorBase + internal class AsyncApiReferenceCollector : AsyncApiVisitorBase { private readonly List references = new(); private AsyncApiWorkspace workspace; - public AsyncApiRemoteReferenceCollector( + public AsyncApiReferenceCollector( AsyncApiWorkspace workspace) { this.workspace = workspace; @@ -34,7 +33,7 @@ public IEnumerable References /// public override void Visit(IAsyncApiReferenceable referenceable) { - if (referenceable.Reference != null && referenceable.Reference.IsExternal) + if (referenceable.Reference != null) { if (referenceable.Reference.Workspace == null) { diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 80701d41..6cbca7ed 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -44,7 +44,12 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiServerVariable)] = AsyncApiV2Deserializer.LoadServerVariable, [typeof(AsyncApiTag)] = AsyncApiV2Deserializer.LoadTag, [typeof(AsyncApiMessage)] = AsyncApiV2Deserializer.LoadMessage, + [typeof(AsyncApiMessageTrait)] = AsyncApiV2Deserializer.LoadMessageTrait, [typeof(AsyncApiChannel)] = AsyncApiV2Deserializer.LoadChannel, + [typeof(AsyncApiBindings)] = AsyncApiV2Deserializer.LoadServerBindings, + [typeof(AsyncApiBindings)] = AsyncApiV2Deserializer.LoadChannelBindings, + [typeof(AsyncApiBindings)] = AsyncApiV2Deserializer.LoadMessageBindings, + [typeof(AsyncApiBindings)] = AsyncApiV2Deserializer.LoadOperationBindings, }; /// diff --git a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs index 3b6a2dd2..5d581e22 100644 --- a/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -169,20 +169,16 @@ public T ResolveReference(AsyncApiReference reference) public T ResolveReference(string location) where T : class { - if (string.IsNullOrEmpty(location)) - { - return default; - } - var uri = this.ToLocationUrl(location); if (this.resolvedReferenceRegistry.TryGetValue(uri, out var referenceableValue)) { return referenceableValue as T; } - if (this.artifactsRegistry.TryGetValue(uri, out var json)) + if (this.artifactsRegistry.TryGetValue(uri, out var stream)) { - return (T)(object)json; + stream.Position = 0; + return (T)(object)stream; } return default; diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index ab8a4f52..3509fc1a 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -436,6 +436,29 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect payload.Properties.Count.Should().Be(1); } + [Test] + public void AsyncApiReference_DocumentLevelReferencePointer_DeserializesCorrectly() + { + var yaml = """ + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + message: + title: test message + other: + $ref: "#/channels/workspace" + """; + + var reader = new AsyncApiStringReader(); + var doc = reader.Read(yaml, out var diagnostic); + doc.Channels.Should().HaveCount(2); + doc.Channels["other"].Publish.Message.First().Title.Should().Be("test message"); + } + [Test] public void AsyncApiReference_WithExternalAvroResource_DeserializesCorrectly() { From 7d076280f675a3a30dfa375e7c10da26c9b04ccb Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 31 Jan 2025 11:35:27 +0100 Subject: [PATCH 19/20] async external ref resolution --- .../AsyncApiJsonDocumentReader.cs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 3267d6ee..929d9858 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -99,7 +99,7 @@ public async Task ReadAsync(JsonNode input, CancellationToken cancel { // Parse the AsyncApi Document document = this.context.Parse(input); - this.ResolveReferences(diagnostic, document); + await this.ResolveReferencesAsync(diagnostic, document); } catch (AsyncApiException ex) { @@ -172,6 +172,51 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi return (T)element; } + private async Task ResolveReferencesAsync(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable) + { + if (this.settings.ReferenceResolution == ReferenceResolutionSetting.DoNotResolveReferences) + { + return; + } + + var collector = new AsyncApiReferenceCollector(this.context.Workspace); + var walker = new AsyncApiWalker(collector); + walker.Walk(serializable); + + foreach (var reference in collector.References) + { + if (this.context.Workspace.Contains(reference.Reference.Reference)) + { + continue; + } + + IAsyncApiSerializable component = null; + if (reference.Reference.IsExternal) + { + if (this.settings.ReferenceResolution != ReferenceResolutionSetting.ResolveAllReferences) + { + continue; + } + + component = await this.ResolveExternalReferenceAsync(diagnostic, reference); + } + else + { + var stream = this.context.Workspace.ResolveReference(string.Empty); // get whole document. + component = this.ResolveStreamReference(stream, reference, diagnostic); + } + + if (component == null) + { + diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"Unable to deserialize reference '{reference.Reference.Reference}'")); + continue; + } + + this.context.Workspace.RegisterComponent(reference.Reference.Reference, component); + this.ResolveReferences(diagnostic, component); + } + } + private void ResolveReferences(AsyncApiDiagnostic diagnostic, IAsyncApiSerializable serializable) { if (this.settings.ReferenceResolution == ReferenceResolutionSetting.DoNotResolveReferences) @@ -247,6 +292,36 @@ private IAsyncApiSerializable ResolveExternalReference(AsyncApiDiagnostic diagno } } + private async Task ResolveExternalReferenceAsync(AsyncApiDiagnostic diagnostic, IAsyncApiReferenceable reference) + { + if (reference is null) + { + throw new ArgumentNullException(nameof(reference)); + } + + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); + try + { + Stream stream; + if (this.context.Workspace.Contains(reference.Reference.ExternalResource)) + { + stream = this.context.Workspace.ResolveReference(reference.Reference.ExternalResource); + } + else + { + stream = await loader.LoadAsync(new Uri(reference.Reference.ExternalResource, UriKind.RelativeOrAbsolute)); + this.context.Workspace.RegisterComponent(reference.Reference.ExternalResource, stream); + } + + return this.ResolveStreamReference(stream, reference, diagnostic); + } + catch (AsyncApiException ex) + { + diagnostic.Errors.Add(new AsyncApiError(ex)); + return null; + } + } + private JsonNode ReadToJson(Stream stream) { if (stream != null) From 69739dac68d15fcd72489af30e3538b08c4eff25 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 31 Jan 2025 12:30:52 +0100 Subject: [PATCH 20/20] review fixes --- src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs | 9 +++------ .../AsyncApiJsonDocumentReader.cs | 6 +++--- .../Services/DefaultStreamLoader.cs | 10 ---------- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 4 ---- src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs | 3 ++- 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs index b80488b8..f1412e25 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs @@ -14,19 +14,16 @@ public class AsyncApiDiagnostic : IDiagnostic public AsyncApiVersion SpecificationVersion { get; set; } - public void Append(AsyncApiDiagnostic diagnosticToAdd, string fileNameToAdd = null) + public void Append(AsyncApiDiagnostic diagnosticToAdd) { - var fileNameIsSupplied = !string.IsNullOrEmpty(fileNameToAdd); foreach (var error in diagnosticToAdd.Errors) { - var errMsgWithFileName = fileNameIsSupplied ? $"[File: {fileNameToAdd}] {error.Message}" : error.Message; - this.Errors.Add(new(error.Pointer, errMsgWithFileName)); + this.Errors.Add(new(error.Pointer, error.Message)); } foreach (var warning in diagnosticToAdd.Warnings) { - var warnMsgWithFileName = fileNameIsSupplied ? $"[File: {fileNameToAdd}] {warning.Message}" : warning.Message; - this.Warnings.Add(new(warning.Pointer, warnMsgWithFileName)); + this.Warnings.Add(new(warning.Pointer, warning.Message)); } } } diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index 929d9858..4512fb44 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -213,7 +213,7 @@ private async Task ResolveReferencesAsync(AsyncApiDiagnostic diagnostic, IAsyncA } this.context.Workspace.RegisterComponent(reference.Reference.Reference, component); - this.ResolveReferences(diagnostic, component); + await this.ResolveReferencesAsync(diagnostic, component); } } @@ -269,7 +269,7 @@ private IAsyncApiSerializable ResolveExternalReference(AsyncApiDiagnostic diagno throw new ArgumentNullException(nameof(reference)); } - var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); try { Stream stream; @@ -299,7 +299,7 @@ private async Task ResolveExternalReferenceAsync(AsyncApi throw new ArgumentNullException(nameof(reference)); } - var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(this.settings); + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); try { Stream stream; diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs index aaaf639b..3d949a23 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs @@ -12,12 +12,6 @@ namespace LEGO.AsyncAPI.Readers.Services internal class DefaultStreamLoader : IStreamLoader { private static readonly HttpClient HttpClient = new HttpClient(); - private readonly AsyncApiReaderSettings settings; - - public DefaultStreamLoader(AsyncApiReaderSettings settings) - { - this.settings = settings; - } public Stream Load(Uri uri) { @@ -27,11 +21,9 @@ public Stream Load(Uri uri) { case "file": return File.OpenRead(uri.AbsolutePath); - break; case "http": case "https": return HttpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); - break; default: throw new ArgumentException("Unsupported scheme"); } @@ -51,11 +43,9 @@ public async Task LoadAsync(Uri uri) { case "file": return File.OpenRead(uri.AbsolutePath); - break; case "http": case "https": return await HttpClient.GetStreamAsync(uri); - break; default: throw new ArgumentException("Unsupported scheme"); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 8964e1d3..38386b7b 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -14,10 +14,6 @@ public class AsyncApiReference : IAsyncApiSerializable { private string originalString; - public AsyncApiReference() - { - } - public AsyncApiReference(string reference, ReferenceType? type) { if (string.IsNullOrWhiteSpace(reference)) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs index f19b0160..3e1fa291 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs @@ -24,11 +24,12 @@ public static implicit operator AsyncApiAvroSchema(AvroPrimitiveType type) public abstract void SerializeV2(IAsyncApiWriter writer); public virtual bool TryGetAs(out T result) -where T : AsyncApiAvroSchema + where T : AsyncApiAvroSchema { result = this as T; return result != null; } + public virtual bool Is() where T : AsyncApiAvroSchema {