From 559d002f233cef266c7e8ae4a0f9af8d2799c488 Mon Sep 17 00:00:00 2001 From: Dec Kolakowski <51292634+dpwdec@users.noreply.github.com> Date: Wed, 15 May 2024 11:43:54 +0100 Subject: [PATCH 01/29] feat(references)!: add basic scaffolding for external ref resolution (#168) Co-authored-by: lego-10-01-06[bot] <119427331+lego-10-01-06[bot]@users.noreply.github.com> Co-authored-by: Alex Wichmann Co-authored-by: gokerakc Co-authored-by: VisualBean Co-authored-by: Byron Mayne Co-authored-by: James Thompson Co-authored-by: dec.kolakowski BREAKING CHANGE: Change the `ReferenceResolution` enum. --- README.md | 42 +++ .../AsyncApiExternalReferenceResolver.cs | 245 ++++++++++++++++++ .../AsyncApiJsonDocumentReader.cs | 40 ++- .../AsyncApiReaderSettings.cs | 16 +- .../IAsyncApiExternalReferenceReader.cs | 14 + .../Services/AsyncApiReferenceResolver.cs | 5 +- .../Models/AsyncApiReference_Should.cs | 113 +++++++- 7 files changed, 461 insertions(+), 14 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs create mode 100644 src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs diff --git a/README.md b/README.md index fe9189f5..e15bf00d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,48 @@ var stream = await httpClient.GetStreamAsync("master/examples/streetlights-kafka var asyncApiDocument = new AsyncApiStreamReader().Read(stream, out var diagnostic); ``` +#### Reading External $ref + +You can read externally referenced AsyncAPI documents by setting the `ReferenceResolution` property of the `AsyncApiReaderSettings` object to `ReferenceResolutionSetting.ResolveAllReferences` and providing an implementation for the `IAsyncApiExternalReferenceReader` interface. This interface contains a single method to which the built AsyncAPI.NET reader library will pass the location content contained in a `$ref` property (usually some form of path) and interface will return the content which is retrieved from wherever the `$ref` points to as a `string`. The AsyncAPI.NET reader will then automatically infer the `T` type of the content and recursively parse the external content into an AsyncAPI document as a child of the original document that contained the `$ref`. This means that you can have externally referenced documents that themselves contain external references. + +This interface allows users to load the content of their external reference however and from whereever is required. A new instance of the implementor of `IAsyncApiExternalReferenceReader` should be registered with the `ExternalReferenceReader` property of the `AsyncApiReaderSettings` when creating the reader from which the `GetExternalResource` method will be called when resolving external references. + +Below is a very simple example of implementation for `IAsyncApiExternalReferenceReader` that simply loads a file and returns it as a string found at the reference endpoint. +```csharp +using System.IO; + +public class AsyncApiExternalFileSystemReader : IAsyncApiExternalReferenceReader +{ + public string GetExternalResource(string reference) + { + return File.ReadAllText(reference); + } +} +``` + +This can then be configured in the reader as follows: +```csharp +var settings = new AsyncApiReaderSettings +{ + ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + ExternalReferenceReader = new AsyncApiExternalFileSystemReader(), +}; +var reader = new AsyncApiStringReader(settings); +``` + +This would function for a AsyncAPI document with following reference to load the content of `message.yaml` as a `AsyncApiMessage` object inline with the document object. +```yaml +asyncapi: 2.3.0 +info: + title: test + version: 1.0.0 +channels: + workspace: + publish: + message: + $ref: "../../../message.yaml" +``` + ### Bindings To add support for reading bindings, simply add the bindings you wish to support, to the `Bindings` collection of `AsyncApiReaderSettings`. There is a nifty helper to add different types of bindings, or like in the example `All` of them. diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs new file mode 100644 index 00000000..ac794d2a --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -0,0 +1,245 @@ +using LEGO.AsyncAPI.Readers.Exceptions; +using LEGO.AsyncAPI.Services; + +namespace LEGO.AsyncAPI.Readers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Exceptions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + + /// + /// 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); + this.ResolveObject(message.Payload, r => message.Payload = r); + this.ResolveList(message.Traits); + this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); + this.ResolveObject(message.Bindings, r => message.Bindings = r); + } + + public override void Visit(AsyncApiServer server) + { + this.ResolveObject(server.Bindings, r => server.Bindings = r); + } + + /// + /// 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 3a973654..d8cea285 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -1,5 +1,7 @@ // Copyright (c) The LEGO Group. All rights reserved. +using System; + namespace LEGO.AsyncAPI.Readers { using System.Collections.Generic; @@ -12,6 +14,7 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; + using LEGO.AsyncAPI.Services; using LEGO.AsyncAPI.Validations; /// @@ -165,23 +168,48 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) { - var errors = new List(); - - // Resolve References if requested switch (this.settings.ReferenceResolution) { - case ReferenceResolutionSetting.ResolveReferences: - errors.AddRange(document.ResolveReferences()); + 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); + } + + 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) { diagnostic.Errors.Add(item); } } + + private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + { + var resolver = new AsyncApiExternalReferenceResolver(document, this.settings); + var walker = new AsyncApiWalker(resolver); + walker.Walk(document); + + foreach (var error in resolver.Errors) + { + diagnostic.Errors.Add(error); + } + } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index 6ca2e129..c9534d20 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -18,9 +18,14 @@ public enum ReferenceResolutionSetting DoNotResolveReferences, /// - /// Resolve internal component references and inline them. + /// Resolve all references and inline them. /// - ResolveReferences, + ResolveInternalReferences, + + /// + /// Resolve internal component references and inline them while leaving external references as placeholder objects with an AsyncApiReference instance and UnresolvedReference set to true. + /// + ResolveAllReferences, } /// @@ -32,7 +37,7 @@ public class AsyncApiReaderSettings : AsyncApiSettings /// Indicates how references in the source document should be handled. /// public ReferenceResolutionSetting ReferenceResolution { get; set; } = - ReferenceResolutionSetting.ResolveReferences; + ReferenceResolutionSetting.ResolveInternalReferences; /// /// Dictionary of parsers for converting extensions into strongly typed classes. @@ -57,5 +62,10 @@ public IEnumerable> /// from an object. /// public bool LeaveStreamOpen { get; set; } + + /// + /// External reference reader implementation provided by users for reading external resources. + /// + public IAsyncApiExternalReferenceReader ExternalReferenceReader { 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 new file mode 100644 index 00000000..28202263 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs @@ -0,0 +1,14 @@ +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/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index da6195c6..516c8aac 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -1,5 +1,4 @@ // Copyright (c) The LEGO Group. All rights reserved. - namespace LEGO.AsyncAPI.Services { using System; @@ -17,7 +16,8 @@ internal class AsyncApiReferenceResolver : AsyncApiVisitorBase private AsyncApiDocument currentDocument; private List errors = new List(); - public AsyncApiReferenceResolver(AsyncApiDocument currentDocument) + public AsyncApiReferenceResolver( + AsyncApiDocument currentDocument) { this.currentDocument = currentDocument; } @@ -207,6 +207,7 @@ private void ResolveMap(IDictionary map) private T ResolveReference(AsyncApiReference reference) where T : class, IAsyncApiReferenceable, new() { + // external references are resolved by the AsyncApiExternalReferenceResolver if (reference.IsExternal) { return new() diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index c4491e40..9e139cc4 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -167,7 +167,7 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference() var settings = new AsyncApiReaderSettings() { - ReferenceResolution = ReferenceResolutionSetting.ResolveReferences, + ReferenceResolution = ReferenceResolutionSetting.ResolveInternalReferences, }; var reader = new AsyncApiStringReader(settings); @@ -187,7 +187,7 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference() } [Test] - public void AsyncApiDocument_WithExternalReference_DoesNotResolve() + public void AsyncApiDocument_WithNoConfiguredExternalReferenceReader_ThrowsError() { // Arrange var actual = """ @@ -202,7 +202,38 @@ public void AsyncApiDocument_WithExternalReference_DoesNotResolve() var settings = new AsyncApiReaderSettings() { - ReferenceResolution = ReferenceResolutionSetting.ResolveReferences, + 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() + { + // 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.ResolveInternalReferences, }; var reader = new AsyncApiStringReader(settings); @@ -250,5 +281,81 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv .Should() .BePlatformAgnosticEquivalentTo(actual); } + + [Test] + public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrectly() + { + var yaml = """ + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + message: + $ref: "./some/path/to/external/message.yaml" + """; + var settings = new AsyncApiReaderSettings + { + ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + ExternalReferenceReader = new MockExternalReferenceReader(), + }; + var reader = new AsyncApiStringReader(settings); + var doc = reader.Read(yaml, out var diagnostic); + var message = doc.Channels["workspace"].Publish.Message.First(); + message.Name.Should().Be("Test"); + message.Payload.Properties.Count.Should().Be(3); + } + } + + public class MockExternalReferenceReader : IAsyncApiExternalReferenceReader + { + public string Load(string reference) + { + 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 + """; + } } } \ No newline at end of file From d4fe2434aaad20fa7597f2c21ce68369ce76cad5 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 3 Jun 2024 12:49:30 +0200 Subject: [PATCH 02/29] feat(message)!: apache avro payload support (#170) BREAKING CHANGE: Changes the type of `Message.Payload` to be `IAsyncApiMessagePayload`. --- .../AMQP/AMQPOperationBinding.cs | 1 - .../Http/HttpMessageBinding.cs | 2 +- .../Http/HttpOperationBinding.cs | 2 +- .../Kafka/KafkaMessageBinding.cs | 2 +- .../Kafka/KafkaOperationBinding.cs | 4 +- src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs | 2 +- .../MQTT/MQTTMessageBinding.cs | 2 +- .../MQTT/MQTTOperationBinding.cs | 3 - src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs | 4 +- .../Sqs/RedrivePolicy.cs | 2 +- .../Sqs/SqsOperationBinding.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs | 2 +- .../WebSockets/WebSocketsChannelBinding.cs | 4 +- .../AsyncApiExternalReferenceResolver.cs | 6 +- .../AsyncApiJsonDocumentReader.cs | 2 - .../AsyncApiTextReader.cs | 1 - ...AsyncApiUnsupportedSpecVersionException.cs | 2 +- .../IAsyncApiExternalReferenceReader.cs | 2 +- .../ParseNodes/MapNode.cs | 8 + .../ParseNodes/PropertyNode.cs | 2 +- .../ParseNodes/ValueNode.cs | 4 +- .../V2/AsyncApiAvroSchemaDeserializer.cs | 190 +++++++++ .../V2/AsyncApiComponentsDeserializer.cs | 2 +- .../V2/AsyncApiMessageDeserializer.cs | 58 ++- .../V2/AsyncApiMessageTraitDeserializer.cs | 2 +- .../V2/AsyncApiParameterDeserializer.cs | 2 +- .../V2/AsyncApiSchemaDeserializer.cs | 27 +- .../V2/AsyncApiV2VersionService.cs | 5 +- src/LEGO.AsyncAPI/AsyncApiVersion.cs | 2 +- src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs | 1 - .../Models/AsyncApiAvroSchemaPayload.cs | 53 +++ src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 6 +- .../Models/AsyncApiSchemaPayload.cs | 127 ++++++ .../Models/AsyncApiSerializableExtensions.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 47 +++ src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 77 ++++ src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 96 +++++ src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 66 +++ src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 45 ++ .../Models/Avro/AvroPrimitive.cs | 47 +++ .../Models/Avro/AvroPrimitiveType.cs | 33 ++ src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 71 ++++ src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 46 +++ src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 50 +++ .../Models/Interfaces/IAsyncApiPayload.cs | 8 + src/LEGO.AsyncAPI/Models/ParameterLocation.cs | 4 +- src/LEGO.AsyncAPI/Models/ReferenceType.cs | 2 +- .../Services/AsyncApiReferenceResolver.cs | 8 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 7 +- .../Rules/AsyncApiOAuthFlowRules.cs | 2 +- .../Writers/AsyncApiWriterException.cs | 2 +- .../Writers/AsyncApiWriterExtensions.cs | 16 + .../Writers/AsyncApiYamlWriter.cs | 2 +- .../SpecialCharacterStringExtensions.cs | 5 - .../AsyncApiDocumentBuilder.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 7 +- .../AsyncApiReaderTests.cs | 1 - .../Bindings/StringOrStringList_Should.cs | 1 - .../FluentAssertionExtensions.cs | 1 - .../Models/AsyncApiAnyTests.cs | 5 +- .../Models/AsyncApiChannel_Should.cs | 2 +- .../Models/AsyncApiMessage_Should.cs | 87 +++- .../Models/AsyncApiOperation_Should.cs | 1 - .../Models/AsyncApiReference_Should.cs | 28 +- .../Models/AsyncApiSchema_Should.cs | 25 +- .../Models/AvroSchema_Should.cs | 388 ++++++++++++++++++ .../Serialization/AsyncApiYamlWriterTests.cs | 3 +- test/LEGO.AsyncAPI.Tests/TestBase.cs | 4 +- 71 files changed, 1607 insertions(+), 126 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs create mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs create mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroField.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs create mode 100644 src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs create mode 100644 test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs diff --git a/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs index 86dc74ef..3a2e6a35 100644 --- a/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs @@ -5,7 +5,6 @@ namespace LEGO.AsyncAPI.Bindings.AMQP using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models; - using LEGO.AsyncAPI.Readers; using LEGO.AsyncAPI.Readers.ParseNodes; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs index 7a5731ef..cbf65bc4 100644 --- a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs @@ -42,7 +42,7 @@ public override void SerializeProperties(IAsyncApiWriter writer) protected override FixedFieldMap FixedFieldMap => new() { { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } }, - { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } }, + { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } }, }; } } diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs index f70858c2..7b88d31b 100644 --- a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs @@ -63,7 +63,7 @@ public override void SerializeProperties(IAsyncApiWriter writer) { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } }, { "type", (a, n) => { a.Type = n.GetScalarValue().GetEnumFromDisplayName(); } }, { "method", (a, n) => { a.Method = n.GetScalarValue(); } }, - { "query", (a, n) => { a.Query = JsonSchemaDeserializer.LoadSchema(n); } }, + { "query", (a, n) => { a.Query = AsyncApiSchemaDeserializer.LoadSchema(n); } }, }; public override string BindingKey => "http"; diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs index 2f665560..2e57b50a 100644 --- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs @@ -67,7 +67,7 @@ public override void SerializeProperties(IAsyncApiWriter writer) protected override FixedFieldMap FixedFieldMap => new() { { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } }, - { "key", (a, n) => { a.Key = JsonSchemaDeserializer.LoadSchema(n); } }, + { "key", (a, n) => { a.Key = AsyncApiSchemaDeserializer.LoadSchema(n); } }, { "schemaIdLocation", (a, n) => { a.SchemaIdLocation = n.GetScalarValue(); } }, { "schemaIdPayloadEncoding", (a, n) => { a.SchemaIdPayloadEncoding = n.GetScalarValue(); } }, { "schemaLookupStrategy", (a, n) => { a.SchemaLookupStrategy = n.GetScalarValue(); } }, diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs index 53db7ae0..b6c6e046 100644 --- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs @@ -28,8 +28,8 @@ public class KafkaOperationBinding : OperationBinding protected override FixedFieldMap FixedFieldMap => new() { { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } }, - { "groupId", (a, n) => { a.GroupId = JsonSchemaDeserializer.LoadSchema(n); } }, - { "clientId", (a, n) => { a.ClientId = JsonSchemaDeserializer.LoadSchema(n); } }, + { "groupId", (a, n) => { a.GroupId = AsyncApiSchemaDeserializer.LoadSchema(n); } }, + { "clientId", (a, n) => { a.ClientId = AsyncApiSchemaDeserializer.LoadSchema(n); } }, }; /// diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs index 23118bfa..955ad90f 100644 --- a/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs +++ b/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs @@ -2,9 +2,9 @@ namespace LEGO.AsyncAPI.Bindings.MQTT { + using System; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - using System; public class LastWill : IAsyncApiElement { diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs index b48e5ae9..597cc4c7 100644 --- a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs @@ -57,7 +57,7 @@ public override void SerializeProperties(IAsyncApiWriter writer) protected override FixedFieldMap FixedFieldMap => new() { { "payloadFormatIndicator", (a, n) => { a.PayloadFormatIndicator = n.GetIntegerValueOrDefault(); } }, - { "correlationData", (a, n) => { a.CorrelationData = JsonSchemaDeserializer.LoadSchema(n); } }, + { "correlationData", (a, n) => { a.CorrelationData = AsyncApiSchemaDeserializer.LoadSchema(n); } }, { "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } }, { "responseTopic", (a, n) => { a.ResponseTopic = n.GetScalarValue(); } }, }; diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs index d3155ecd..f58eede9 100644 --- a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs @@ -3,9 +3,6 @@ namespace LEGO.AsyncAPI.Bindings.MQTT { using System; - using System.Collections.Generic; - using LEGO.AsyncAPI.Models; - using LEGO.AsyncAPI.Readers; using LEGO.AsyncAPI.Readers.ParseNodes; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs index 23f69f52..d44a0c9e 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs @@ -11,7 +11,7 @@ namespace LEGO.AsyncAPI.Bindings.Sns public class Ordering : IAsyncApiExtensible { /// - /// What type of SNS Topic is this? + /// What type of SNS Topic is this?. /// public OrderingType Type { get; set; } diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs index 7f3771f0..c92eaf94 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs @@ -19,7 +19,7 @@ public class Statement : IAsyncApiExtensible public StringOrStringList Principal { get; set; } /// - /// The SNS permission being allowed or denied e.g. sns:Publish + /// The SNS permission being allowed or denied e.g. sns:Publish. /// public StringOrStringList Action { get; set; } diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs index 33166af5..eb3d256a 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs @@ -16,7 +16,7 @@ public class Queue : IAsyncApiExtensible public string Name { get; set; } /// - /// Is this a FIFO queue? + /// Is this a FIFO queue?. /// public bool FifoQueue { get; set; } @@ -56,7 +56,7 @@ public class Queue : IAsyncApiExtensible public RedrivePolicy RedrivePolicy { get; set; } /// - /// The security policy for the SQS Queue + /// The security policy for the SQS Queue. /// public Policy Policy { get; set; } diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs index 923d668c..538b1edf 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs @@ -3,9 +3,9 @@ namespace LEGO.AsyncAPI.Bindings.Sqs { using System; + using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - using System.Collections.Generic; public class RedrivePolicy : IAsyncApiExtensible { diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs index d8eb43dd..98110209 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs @@ -10,7 +10,7 @@ namespace LEGO.AsyncAPI.Bindings.Sqs public class SqsOperationBinding : OperationBinding { /// - /// Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object + /// Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object. /// public List Queues { get; set; } diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs index 9518d2d4..0bed331f 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs @@ -19,7 +19,7 @@ public class Statement : IAsyncApiExtensible public StringOrStringList Principal { get; set; } /// - /// The SNS permission being allowed or denied e.g. sns:Publish + /// The SNS permission being allowed or denied e.g. sns:Publish. /// public StringOrStringList Action { get; set; } diff --git a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs index c87393bb..8827bea4 100644 --- a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs @@ -31,8 +31,8 @@ public class WebSocketsChannelBinding : ChannelBinding { { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } }, { "method", (a, n) => { a.Method = n.GetScalarValue(); } }, - { "query", (a, n) => { a.Query = JsonSchemaDeserializer.LoadSchema(n); } }, - { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } }, + { "query", (a, n) => { a.Query = AsyncApiSchemaDeserializer.LoadSchema(n); } }, + { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } }, }; public override void SerializeProperties(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index ac794d2a..2c111773 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -6,7 +6,6 @@ namespace LEGO.AsyncAPI.Readers using System; using System.Collections.Generic; using System.Linq; - using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; @@ -91,7 +90,10 @@ public override void Visit(AsyncApiOperation operation) public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - this.ResolveObject(message.Payload, r => message.Payload = 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); diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index d8cea285..6b340651 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -1,7 +1,5 @@ // Copyright (c) The LEGO Group. All rights reserved. -using System; - namespace LEGO.AsyncAPI.Readers { using System.Collections.Generic; diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs index eeece6a7..8d1eafc6 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Readers { using System.IO; using System.Linq; - using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; diff --git a/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs b/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs index d68da4bf..590b3d73 100644 --- a/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs +++ b/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs @@ -29,7 +29,7 @@ public AsyncApiUnsupportedSpecVersionException(string specificationVersion) /// inner exception. /// /// Version that caused this exception to be thrown. - /// The setting used for reading and writing + /// The setting used for reading and writing. /// Inner exception that caused this exception to be thrown. public AsyncApiUnsupportedSpecVersionException(string specificationVersion, Exception innerException) : base(string.Format(CultureInfo.InvariantCulture, MessagePattern, specificationVersion), innerException) diff --git a/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs index 28202263..6eef5608 100644 --- a/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs +++ b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs @@ -1,7 +1,7 @@ namespace LEGO.AsyncAPI.Readers; /// -/// Interface that provides method for reading external references.å +/// Interface that provides method for reading external references.å. /// public interface IAsyncApiExternalReferenceReader { diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs index b087c875..63bea6fc 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs @@ -214,6 +214,14 @@ public override AsyncApiAny CreateAny() return new AsyncApiAny(this.node); } + public void ParseFields(ref T parentInstance, IDictionary> fixedFields, IDictionary, Action> patternFields) + { + foreach (var propertyNode in this) + { + propertyNode.ParseField(parentInstance, fixedFields, patternFields); + } + } + private string ToScalarValue(JsonNode node) { var scalarNode = node is JsonValue value ? value : throw new AsyncApiException($"Expected scalar value"); diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs index e0359659..11120606 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs @@ -79,7 +79,7 @@ public void ParseField( else { this.Context.Diagnostic.Errors.Add( - new AsyncApiError("", $"{this.Name} is not a valid property at {this.Context.GetLocation()}")); + new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}")); } } } diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs index 201ab6f7..76add0b2 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs @@ -2,11 +2,11 @@ namespace LEGO.AsyncAPI.Readers.ParseNodes { + using System; + using System.Text.Json.Nodes; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers.Exceptions; - using System; - using System.Text.Json.Nodes; public class ValueNode : ParseNode { diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs new file mode 100644 index 00000000..013ec856 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -0,0 +1,190 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers +{ + using System; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Readers.Exceptions; + using LEGO.AsyncAPI.Readers.ParseNodes; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiAvroSchemaDeserializer + { + private static readonly FixedFieldMap FieldFixedFields = new() + { + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "type", (a, n) => a.Type = LoadSchema(n) }, + { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + { "default", (a, n) => a.Default = n.CreateAny() }, + { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "order", (a, n) => a.Order = n.GetScalarValue().GetEnumFromDisplayName() }, + }; + + private static readonly FixedFieldMap RecordFixedFields = new() + { + { "type", (a, n) => { } }, + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "fields", (a, n) => a.Fields = n.CreateList(LoadField) }, + }; + + private static readonly FixedFieldMap EnumFixedFields = new() + { + { "type", (a, n) => { } }, + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "symbols", (a, n) => a.Symbols = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "default", (a, n) => a.Default = n.GetScalarValue() }, + }; + + private static readonly FixedFieldMap FixedFixedFields = new() + { + { "type", (a, n) => { } }, + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "size", (a, n) => a.Size = int.Parse(n.GetScalarValue(), n.Context.Settings.CultureInfo) }, + }; + + private static readonly FixedFieldMap ArrayFixedFields = new() + { + { "type", (a, n) => { } }, + { "items", (a, n) => a.Items = LoadSchema(n) }, + }; + + private static readonly FixedFieldMap MapFixedFields = new() + { + { "type", (a, n) => { } }, + { "values", (a, n) => a.Values = n.GetScalarValue().GetEnumFromDisplayName() }, + }; + + private static readonly FixedFieldMap UnionFixedFields = new() + { + { "types", (a, n) => a.Types = n.CreateList(LoadSchema) }, + }; + + private static readonly PatternFieldMap RecordMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap FieldMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap EnumMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap FixedMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap ArrayMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap MapMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap UnionMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + public static AvroSchema LoadSchema(ParseNode node) + { + if (node is ValueNode valueNode) + { + return new AvroPrimitive(valueNode.GetScalarValue().GetEnumFromDisplayName()); + } + + if (node is ListNode) + { + var union = new AvroUnion(); + foreach (var item in node as ListNode) + { + union.Types.Add(LoadSchema(item)); + } + + return union; + } + + if (node is MapNode mapNode) + { + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + return new AvroRecord + { + UnresolvedReference = true, + Reference = node.Context.VersionService.ConvertToAsyncApiReference(pointer, ReferenceType.Schema), + }; + } + + var type = mapNode["type"]?.Value.GetScalarValue(); + + switch (type) + { + case "record": + var record = new AvroRecord(); + mapNode.ParseFields(ref record, RecordFixedFields, RecordMetadataPatternFields); + return record; + case "enum": + var @enum = new AvroEnum(); + mapNode.ParseFields(ref @enum, EnumFixedFields, EnumMetadataPatternFields); + return @enum; + case "fixed": + var @fixed = new AvroFixed(); + mapNode.ParseFields(ref @fixed, FixedFixedFields, FixedMetadataPatternFields); + return @fixed; + case "array": + var array = new AvroArray(); + mapNode.ParseFields(ref array, ArrayFixedFields, ArrayMetadataPatternFields); + return array; + case "map": + var map = new AvroMap(); + mapNode.ParseFields(ref map, MapFixedFields, MapMetadataPatternFields); + return map; + case "union": + var union = new AvroUnion(); + mapNode.ParseFields(ref union, UnionFixedFields, UnionMetadataPatternFields); + return union; + default: + throw new InvalidOperationException($"Unsupported type: {type}"); + } + } + + throw new AsyncApiReaderException("Invalid node type"); + } + + private static AvroField LoadField(ParseNode node) + { + var mapNode = node.CheckMapNode("field"); + var field = new AvroField(); + + mapNode.ParseFields(ref field, FieldFixedFields, FieldMetadataPatternFields); + + return field; + + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs index 3b63db28..df13308a 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, JsonSchemaDeserializer.LoadSchema) }, + { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, AsyncApiSchemaDeserializer.LoadSchema) }, { "servers", (a, n) => a.Servers = n.CreateMapWithReference(ReferenceType.Server, LoadServer) }, { "channels", (a, n) => a.Channels = n.CreateMapWithReference(ReferenceType.Channel, LoadChannel) }, { "messages", (a, n) => a.Messages = n.CreateMapWithReference(ReferenceType.Message, LoadMessage) }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 4c16bf22..2670d88a 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -2,12 +2,13 @@ namespace LEGO.AsyncAPI.Readers { + using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.ParseNodes; - using System.Collections.Generic; - using System.Linq; /// /// Class containing logic to deserialize AsyncApi document into @@ -21,10 +22,10 @@ internal static partial class AsyncApiV2Deserializer "messageId", (a, n) => { a.MessageId = n.GetScalarValue(); } }, { - "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } + "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } }, { - "payload", (a, n) => { a.Payload = JsonSchemaDeserializer.LoadSchema(n); } + "payload", (a, n) => { a.Payload = null; /* resolved after the initial run */ } }, { "correlationId", (a, n) => { a.CorrelationId = LoadCorrelationId(n); } @@ -64,7 +65,39 @@ internal static partial class AsyncApiV2Deserializer }, }; - static readonly IEnumerable SupportedSchemaFormats = new List + public static IAsyncApiMessagePayload LoadJsonSchemaPayload(ParseNode n) + { + return LoadPayload(n, null); + } + + public static IAsyncApiMessagePayload LoadAvroPayload(ParseNode n) + { + return LoadPayload(n, "application/vnd.apache.avro"); + } + + private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) + { + + if (n == null) + { + return null; + } + + switch (format) + { + case null: + case "": + case var _ when SupportedJsonSchemaFormats.Where(s => format.StartsWith(s)).Any(): + return new AsyncApiJsonSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n)); + case var _ when SupportedAvroSchemaFormats.Where(s => format.StartsWith(s)).Any(): + return new AsyncApiAvroSchemaPayload(AsyncApiAvroSchemaDeserializer.LoadSchema(n)); + default: + var supportedFormats = SupportedJsonSchemaFormats.Concat(SupportedAvroSchemaFormats); + throw new AsyncApiException($"'Could not deserialize Payload. Supported formats are {string.Join(", ", supportedFormats)}"); + } + } + + static readonly IEnumerable SupportedJsonSchemaFormats = new List { "application/vnd.aai.asyncapi+json", "application/vnd.aai.asyncapi+yaml", @@ -73,11 +106,21 @@ internal static partial class AsyncApiV2Deserializer "application/schema+yaml;version=draft-07", }; + static readonly IEnumerable SupportedAvroSchemaFormats = new List + { + "application/vnd.apache.avro", + "application/vnd.apache.avro+json", + "application/vnd.apache.avro+yaml", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0", + }; + private static string LoadSchemaFormat(string schemaFormat) { - if (!SupportedSchemaFormats.Where(s => schemaFormat.StartsWith(s)).Any()) + var supportedFormats = SupportedJsonSchemaFormats.Concat(SupportedAvroSchemaFormats); + if (!supportedFormats.Where(s => schemaFormat.StartsWith(s)).Any()) { - throw new AsyncApiException($"'{schemaFormat}' is not a supported format. Supported formats are {string.Join(", ", SupportedSchemaFormats)}"); + throw new AsyncApiException($"'{schemaFormat}' is not a supported format. Supported formats are {string.Join(", ", supportedFormats)}"); } return schemaFormat; @@ -100,6 +143,7 @@ public static AsyncApiMessage LoadMessage(ParseNode node) var message = new AsyncApiMessage(); ParseMap(mapNode, message, messageFixedFields, messagePatternFields); + message.Payload = LoadPayload(mapNode["payload"]?.Value, message.SchemaFormat); return message; } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs index eca8af64..0d229e92 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs @@ -11,7 +11,7 @@ internal static partial class AsyncApiV2Deserializer private static FixedFieldMap messageTraitFixedFields = new() { { "messageId", (a, n) => { a.MessageId = n.GetScalarValue(); } }, - { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } }, + { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } }, { "correlationId", (a, n) => { a.CorrelationId = LoadCorrelationId(n); } }, { "schemaFormat", (a, n) => { a.SchemaFormat = n.GetScalarValue(); } }, { "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } }, diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs index bff810f1..c77f48f4 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs @@ -11,7 +11,7 @@ internal static partial class AsyncApiV2Deserializer private static FixedFieldMap parameterFixedFields = new() { { "description", (a, n) => { a.Description = n.GetScalarValue(); } }, - { "schema", (a, n) => { a.Schema = JsonSchemaDeserializer.LoadSchema(n); } }, + { "schema", (a, n) => { a.Schema = AsyncApiSchemaDeserializer.LoadSchema(n); } }, { "location", (a, n) => { a.Location = n.GetScalarValue(); } }, }; diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs index b99fdef8..b0c10de7 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs @@ -9,7 +9,7 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Readers.ParseNodes; using LEGO.AsyncAPI.Writers; - public class JsonSchemaDeserializer + public class AsyncApiSchemaDeserializer { private static readonly FixedFieldMap schemaFixedFields = new() { @@ -215,28 +215,6 @@ public class JsonSchemaDeserializer { s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, AsyncApiV2Deserializer.LoadExtension(p, n)) }, }; - private static readonly AnyFieldMap schemaAnyFields = new() - { - { - AsyncApiConstants.Default, - new AnyFieldMapParameter( - s => s.Default, - (s, v) => s.Default = v, - s => s) - }, - }; - - private static readonly AnyListFieldMap schemaAnyListFields = new() - { - { - AsyncApiConstants.Enum, - new AnyListFieldMapParameter( - s => s.Enum, - (s, v) => s.Enum = v, - s => s) - }, - }; - public static AsyncApiSchema LoadSchema(ParseNode node) { var mapNode = node.CheckMapNode(AsyncApiConstants.Schema); @@ -259,9 +237,6 @@ public static AsyncApiSchema LoadSchema(ParseNode node) propertyNode.ParseField(schema, schemaFixedFields, schemaPatternFields); } - AsyncApiV2Deserializer.ProcessAnyFields(mapNode, schema, schemaAnyFields); - AsyncApiV2Deserializer.ProcessAnyListFields(mapNode, schema, schemaAnyListFields); - return schema; } } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 10edd3ba..4acc3395 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -35,7 +35,10 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiOAuthFlows)] = AsyncApiV2Deserializer.LoadOAuthFlows, [typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation, [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, - [typeof(AsyncApiSchema)] = JsonSchemaDeserializer.LoadSchema, + [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema, + [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, + [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, + [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, [typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme, [typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer, diff --git a/src/LEGO.AsyncAPI/AsyncApiVersion.cs b/src/LEGO.AsyncAPI/AsyncApiVersion.cs index c5be2cc8..dbac63de 100644 --- a/src/LEGO.AsyncAPI/AsyncApiVersion.cs +++ b/src/LEGO.AsyncAPI/AsyncApiVersion.cs @@ -5,7 +5,7 @@ namespace LEGO.AsyncAPI public enum AsyncApiVersion { /// - /// Represents AsyncAPI V2 spec + /// Represents AsyncAPI V2 spec. /// AsyncApi2_0, } diff --git a/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs b/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs index 5c5f2b31..45b764b3 100644 --- a/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs +++ b/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; using LEGO.AsyncAPI.Models.Interfaces; diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs new file mode 100644 index 00000000..596115ba --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -0,0 +1,53 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload + { + public AvroSchema Schema { get; set; } + + public AsyncApiAvroSchemaPayload(AvroSchema schema) + { + this.Schema = schema; + } + + public AsyncApiAvroSchemaPayload() + { + } + + public bool TryGetAs(out T schema) + where T : AvroSchema + { + schema = this.Schema as T; + return schema != null; + } + + 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/AsyncApiMessage.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs index 2c627541..b4848bf6 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs @@ -25,7 +25,7 @@ public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiReferenceable, IAsy /// /// 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 AsyncApiSchema Payload { get; set; } + public IAsyncApiMessagePayload Payload { get; set; } /// /// definition of the correlation ID used for message tracing or matching. diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 4f9660d1..9475310e 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -15,7 +15,7 @@ public class AsyncApiReference : IAsyncApiSerializable /// 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 + /// 2. a Url, for example: http://localhost/pet.json. /// public string ExternalResource { get; set; } @@ -35,7 +35,7 @@ public class AsyncApiReference : IAsyncApiSerializable public AsyncApiDocument HostDocument { get; set; } = null; /// - /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment + /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment. /// public bool IsFragment { get; set; } = false; @@ -71,7 +71,7 @@ public string Reference } /// - /// Serialize to Async Api v2.4. + /// Serialize to Async Api. /// public void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs new file mode 100644 index 00000000..19db07b2 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -0,0 +1,127 @@ +// 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 AsyncApiSchema schema; + + public AsyncApiJsonSchemaPayload() + { + this.schema = new AsyncApiSchema(); + } + + public AsyncApiJsonSchemaPayload(AsyncApiSchema 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 bool? ExclusiveMaximum { get => this.schema.ExclusiveMaximum; set => this.schema.ExclusiveMaximum = value; } + + public double? Minimum { get => this.schema.Minimum; set => this.schema.Minimum = value; } + + public bool? 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 AsyncApiSchema Not { get => this.schema.Not; set => this.schema.Not = value; } + + public AsyncApiSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; } + + public AsyncApiSchema If { get => this.schema.If; set => this.schema.If = value; } + + public AsyncApiSchema Then { get => this.schema.Then; set => this.schema.Then = value; } + + public AsyncApiSchema 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 AsyncApiSchema 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 AsyncApiSchema 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 AsyncApiSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } + + public static implicit operator AsyncApiSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; + + public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiSchema schema) => new AsyncApiJsonSchemaPayload(schema); + + public void SerializeV2(IAsyncApiWriter writer) + { + this.schema.SerializeV2(writer); + } + + public void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + this.schema.SerializeV2WithoutReference(writer); + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs index 1d1bd35a..91167e19 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs @@ -33,7 +33,7 @@ public static void SerializeAsJson(this T element, Stream stream, AsyncApiVer /// The AsyncApi element. /// The output stream. /// The AsyncApi specification version. - /// The settings used for writing + /// The settings used for writing. public static void SerializeAsJson(this T element, Stream stream, AsyncApiVersion specificationVersion, AsyncApiWriterSettings settings) where T : IAsyncApiSerializable { @@ -60,7 +60,7 @@ public static void SerializeAsYaml(this T element, Stream stream, AsyncApiVer /// The AsyncApi element. /// The output stream. /// The AsyncApi specification version. - /// The settings used for writing + /// The settings used for writing. public static void SerializeAsYaml(this T element, Stream stream, AsyncApiVersion specificationVersion, AsyncApiWriterSettings settings) where T : IAsyncApiSerializable { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs new file mode 100644 index 00000000..0958fa3d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -0,0 +1,47 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroArray : AvroSchema + { + public override string Type { get; } = "array"; + + /// + /// The schema of the array's items. + /// + public AvroSchema Items { get; set; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredObject("items", this.Items, (w, f) => f.SerializeV2(w)); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs new file mode 100644 index 00000000..dbfbb627 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -0,0 +1,77 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroEnum : AvroSchema + { + public override string Type { get; } = "enum"; + + /// + /// The name of the schema. Required for named types. See Avro Names. + /// + public string Name { get; set; } + + /// + /// The namespace of the schema. Useful for named types to avoid name conflicts. + /// + public string Namespace { get; set; } + + /// + /// Documentation for the schema. + /// + public string Doc { get; set; } + + /// + /// Alternate names for this enum. + /// + public IList Aliases { get; set; } = new List(); + + /// + /// Listing symbols. All symbols in an enum must be unique. + /// + public IList Symbols { get; set; } = new List(); + + /// + /// A default value for this enumeration. + /// + public string Default { get; set; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalProperty("namespace", this.Namespace); + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); + writer.WriteOptionalProperty("doc", this.Doc); + writer.WriteRequiredCollection("symbols", this.Symbols, (w, s) => w.WriteValue(s)); + writer.WriteOptionalProperty("default", this.Default); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs new file mode 100644 index 00000000..6e8f9ed8 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -0,0 +1,96 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public enum AvroFieldOrder + { + None = 0, + + [Display("ascending")] + Ascending, + + [Display("descending")] + Descending, + + [Display("ignore")] + Ignore, + } + /// + /// Represents a field within an Avro record schema. + /// + public class AvroField : IAsyncApiSerializable + { + /// + /// The name of the field. + /// + public string Name { get; set; } + + /// + /// The type of the field. Can be a primitive type, a complex type, or a union. + /// + public AvroSchema Type { get; set; } + + /// + /// The documentation for the field. + /// + public string Doc { get; set; } + + /// + /// The default value for the field. + /// + public AsyncApiAny Default { get; set; } + + /// + /// The order of the field, can be 'ascending', 'descending', or 'ignore'. + /// + public AvroFieldOrder Order { get; set; } + + /// + /// Alternate names for this record (optional). + /// + public IList Aliases { get; set; } = new List(); + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); + + public void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("name", this.Name); + writer.WriteOptionalObject("type", this.Type, (w, s) => s.SerializeV2(w)); + writer.WriteOptionalProperty("doc", this.Doc); + writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s)); + if (this.Order != AvroFieldOrder.None) + { + writer.WriteOptionalProperty("order", this.Order.GetDisplayName()); + } + + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs new file mode 100644 index 00000000..241e8c1e --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -0,0 +1,66 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroFixed : AvroSchema + { + public override string Type { get; } = "fixed"; + + /// + /// The name of the schema. Required for named types. See Avro Names. + /// + public string Name { get; set; } + + /// + /// The namespace of the schema. Useful for named types to avoid name conflicts. + /// + public string Namespace { get; set; } + + + /// + /// Alternate names for this record. + /// + public IList Aliases { get; set; } = new List(); + + /// + /// Number of bytes per value. + /// + public int Size { get; set; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalProperty("namespace", this.Namespace); + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); + writer.WriteRequiredProperty("size", this.Size); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs new file mode 100644 index 00000000..c66d476b --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -0,0 +1,45 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroMap : AvroSchema + { + public override string Type { get; } = "map"; + + public AvroPrimitiveType Values { get; set; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("values", this.Values.GetDisplayName()); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs new file mode 100644 index 00000000..d7526344 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -0,0 +1,47 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroPrimitive : AvroSchema + { + public override string Type { get; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public AvroPrimitive(AvroPrimitiveType type) + { + this.Type = type.GetDisplayName(); + } + + public AvroPrimitive() + { + } + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteValue(this.Type); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs new file mode 100644 index 00000000..b76e7554 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs @@ -0,0 +1,33 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using LEGO.AsyncAPI.Attributes; + + public enum AvroPrimitiveType + { + [Display("null")] + Null, + + [Display("boolean")] + Boolean, + + [Display("int")] + Int, + + [Display("long")] + Long, + + [Display("float")] + Float, + + [Display("double")] + Double, + + [Display("bytes")] + Bytes, + + [Display("string")] + String, + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs new file mode 100644 index 00000000..04642f5c --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -0,0 +1,71 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroRecord : AvroSchema + { + public override string Type { get; } = "record"; + + /// + /// The name of the schema. Required for named types. See Avro Names. + /// + public string Name { get; set; } + + /// + /// The namespace of the schema. Useful for named types to avoid name conflicts. + /// + public string Namespace { get; set; } + + /// + /// Documentation for the schema. + /// + public string Doc { get; set; } + + /// + /// Alternate names for this record. + /// + public IList Aliases { get; set; } = new List(); + + /// + /// A list of fields contained within this record. + /// + public IList Fields { get; set; } = new List(); + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalProperty("namespace", this.Namespace); + writer.WriteOptionalProperty("doc", this.Doc); + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); + writer.WriteRequiredCollection("fields", this.Fields, (w, s) => s.SerializeV2(w)); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs new file mode 100644 index 00000000..fce1df2d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -0,0 +1,46 @@ +// 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 abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable + { + public abstract string Type { get; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public abstract IDictionary Metadata { get; set; } + + public bool UnresolvedReference { get; set; } + + public AsyncApiReference Reference { get; set; } + + public static implicit operator AvroSchema(AvroPrimitiveType type) + { + return new AvroPrimitive(type); + } + + 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 abstract void SerializeV2WithoutReference(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 new file mode 100644 index 00000000..5762935d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -0,0 +1,50 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroUnion : AvroSchema + { + public override string Type { get; } = "map"; + + /// + /// The types in this union. + /// + public IList Types { get; set; } = new List(); + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public override IDictionary Metadata { get; set; } = new Dictionary(); + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartArray(); + foreach (var type in this.Types) + { + type.SerializeV2(writer); + } + + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndArray(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs new file mode 100644 index 00000000..76112af9 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs @@ -0,0 +1,8 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Interfaces +{ + public interface IAsyncApiMessagePayload : IAsyncApiSerializable, IAsyncApiReferenceable + { + } +} diff --git a/src/LEGO.AsyncAPI/Models/ParameterLocation.cs b/src/LEGO.AsyncAPI/Models/ParameterLocation.cs index 54feb896..bcda720d 100644 --- a/src/LEGO.AsyncAPI/Models/ParameterLocation.cs +++ b/src/LEGO.AsyncAPI/Models/ParameterLocation.cs @@ -7,12 +7,12 @@ namespace LEGO.AsyncAPI.Models public enum ParameterLocation { /// - /// The user + /// The user. /// [Display("user")] User, /// - /// The password + /// The password. /// [Display("password")] Password, diff --git a/src/LEGO.AsyncAPI/Models/ReferenceType.cs b/src/LEGO.AsyncAPI/Models/ReferenceType.cs index 8903dd73..cc7dcaf5 100644 --- a/src/LEGO.AsyncAPI/Models/ReferenceType.cs +++ b/src/LEGO.AsyncAPI/Models/ReferenceType.cs @@ -84,7 +84,7 @@ public enum ReferenceType [Display("headers")] Header, /// - /// The server variable + /// The server variable. /// [Display("serverVariable")] ServerVariable, } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 516c8aac..c37c2804 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -86,7 +86,13 @@ public override void Visit(AsyncApiOperation operation) public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - this.ResolveObject(message.Payload, r => message.Payload = 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); diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index fc5c5186..a2108647 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -501,7 +501,12 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) if (message != null) { this.Walk(AsyncApiConstants.Headers, () => this.Walk(message.Headers)); - this.Walk(AsyncApiConstants.Payload, () => this.Walk(message.Payload)); + if (message.Payload is AsyncApiJsonSchemaPayload payload) + { + this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiSchema)payload)); + } + + // #ToFix Add walking for avro. 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/Rules/AsyncApiOAuthFlowRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs index 457a03ec..66090074 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs @@ -2,9 +2,9 @@ namespace LEGO.AsyncAPI.Validation.Rules { + using System.Linq; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Validations; - using System.Linq; [AsyncApiRule] public static class AsyncApiOAuthFlowRules diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs index 9d89bba3..17229818 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs @@ -2,8 +2,8 @@ namespace LEGO.AsyncAPI.Writers { - using LEGO.AsyncAPI.Exceptions; using System; + using LEGO.AsyncAPI.Exceptions; public class AsyncApiWriterException : AsyncApiException { diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs index 67c4737a..c909e168 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs @@ -204,6 +204,22 @@ public static void WriteOptionalCollection( } } + /// + /// Write the required of collection string. + /// + /// The AsyncApi writer. + /// The property name. + /// The collection values. + /// The collection element writer action. + public static void WriteRequiredCollection( + this IAsyncApiWriter writer, + string name, + IEnumerable elements, + Action action) + { + writer.WriteCollectionInternal(name, elements, action); + } + /// /// Write the optional AsyncApi object/element collection. /// diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs index d0fab76e..f7b32dd9 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs @@ -42,7 +42,7 @@ public AsyncApiYamlWriter(TextWriter textWriter) /// Initializes a new instance of the class. /// /// The text writer. - /// The settings used to read and write yaml + /// The settings used to read and write yaml. public AsyncApiYamlWriter(TextWriter textWriter, AsyncApiWriterSettings settings) : base(textWriter, settings) { diff --git a/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs b/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs index f2f6fbe2..9bce6aa2 100644 --- a/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs +++ b/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs @@ -2,11 +2,6 @@ namespace LEGO.AsyncAPI.Writers { - using System; - using System.Globalization; - using System.Linq; - using System.Text.RegularExpressions; - public static class SpecialCharacterStringExtensions { /// diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs index 3f2ee429..f800a1f7 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs @@ -2,9 +2,9 @@ namespace LEGO.AsyncAPI.Tests { + using System; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; - using System; internal class AsyncApiDocumentBuilder { diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 63e818b4..4cbf1bd4 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; using System.Linq; using FluentAssertions; @@ -493,7 +492,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiJsonSchemaPayload { Reference = new AsyncApiReference() { @@ -518,7 +517,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiJsonSchemaPayload() { Reference = new AsyncApiReference() { @@ -543,7 +542,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiJsonSchemaPayload() { Reference = new AsyncApiReference() { diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index bf19a944..0a4cd5e7 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 LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs index c437f15c..1a7c8681 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests.Bindings { using System; using System.Collections.Generic; - using System.Linq; using FluentAssertions; using LEGO.AsyncAPI.Bindings; using LEGO.AsyncAPI.Models; diff --git a/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs b/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs index 31fec68a..a9a771ab 100644 --- a/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs +++ b/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Tests { using System; - using System.IO; using FluentAssertions; using FluentAssertions.Primitives; using NUnit.Framework; diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs index 6c93e483..1dfdc59a 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs @@ -1,10 +1,9 @@ // Copyright (c) The LEGO Group. All rights reserved. -using LEGO.AsyncAPI.Models; -using NUnit.Framework; -using System; using System.Collections.Generic; using System.Linq; +using LEGO.AsyncAPI.Models; +using NUnit.Framework; namespace LEGO.AsyncAPI.Tests { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index ef24e07b..19c6be71 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs @@ -10,7 +10,7 @@ namespace LEGO.AsyncAPI.Tests.Models using LEGO.AsyncAPI.Models.Interfaces; using NUnit.Framework; - internal class AsyncApiChannel_Should : TestBase + public class AsyncApiChannel_Should : TestBase { [Test] public void AsyncApiChannel_WithWebSocketsBinding_Serializes() diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index a37f1503..538664a8 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.Properties.First().Value.Enum.Should().HaveCount(2); + message.Payload.As().Properties.First().Value.Enum.Should().HaveCount(2); } [Test] @@ -78,7 +78,7 @@ public void AsyncApiMessage_WithUnsupportedSchemaFormat_DeserializesWithError() type: - 'null' - string - schemaFormat: application/vnd.apache.avro;version=1.9.0 + schemaFormat: whatever """; // Act @@ -86,7 +86,7 @@ public void AsyncApiMessage_WithUnsupportedSchemaFormat_DeserializesWithError() // Assert diagnostic.Errors.Should().HaveCount(1); - diagnostic.Errors.First().Message.Should().StartWith("'application/vnd.apache.avro;version=1.9.0' is not a supported format"); + diagnostic.Errors.First().Message.Should().StartWith("'whatever' is not a supported format"); } [Test] @@ -104,7 +104,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() """; var message = new AsyncApiMessage(); - message.Payload = new AsyncApiSchema() + message.Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary() { @@ -120,7 +120,6 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() // Act var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); - var deserializedMessage = new AsyncApiStringReader().ReadFragment(expected, AsyncApiVersion.AsyncApi2_0, out _); // Assert @@ -130,7 +129,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() } [Test] - public void AsyncApiMessage_WithSchemaFormat_Serializes() + public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() { // Arrange var expected = @@ -146,7 +145,7 @@ public void AsyncApiMessage_WithSchemaFormat_Serializes() var message = new AsyncApiMessage(); message.SchemaFormat = "application/vnd.aai.asyncapi+json;version=2.6.0"; - message.Payload = new AsyncApiSchema() + message.Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary() { @@ -169,6 +168,78 @@ public void AsyncApiMessage_WithSchemaFormat_Serializes() message.Should().BeEquivalentTo(deserializedMessage); } + [Test] + public void AsyncApiMessage_WithAvroSchemaFormat_Serializes() + { + // Arrange + var expected = + """ + payload: + type: record + name: User + namespace: com.example + fields: + - name: username + type: string + doc: The username of the user. + default: guest + order: ascending + schemaFormat: application/vnd.apache.avro + """; + + var message = new AsyncApiMessage(); + message.SchemaFormat = "application/vnd.apache.avro"; + message.Payload = new AsyncApiAvroSchemaPayload() + { + Schema = new AvroRecord() + { + Name = "User", + Namespace = "com.example", + Fields = new List + { + new AvroField() + { + Name = "username", + Type = AvroPrimitiveType.String, + Doc = "The username of the user.", + Default = new AsyncApiAny("guest"), + Order = AvroFieldOrder.Ascending, + }, + }, + }, + }; + + // Act + var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + var deserializedMessage = new AsyncApiStringReader().ReadFragment(expected, AsyncApiVersion.AsyncApi2_0, out _); + + // Assert + actual.Should() + .BePlatformAgnosticEquivalentTo(expected); + message.Should().BeEquivalentTo(deserializedMessage); + } + + [Test] + public void AsyncApiMessage_WithAvroAsReference_Deserializes() + { + // Arrange + var input = + """ + schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0' + payload: + $ref: 'path/to/user-create.avsc/#UserCreate' + """; + + // Act + var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); + + // Assert + deserializedMessage.Payload.Reference.Should().NotBeNull(); + deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue(); + deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue(); + + } + [Test] public void AsyncApiMessage_WithFilledObject_Serializes() { @@ -257,7 +328,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() }), }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs index 5597b211..847da7be 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Tests.Models { using System; - using System.Globalization; using System.IO; using FluentAssertions; using LEGO.AsyncAPI.Bindings.Http; diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 9e139cc4..41972784 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -2,12 +2,11 @@ namespace LEGO.AsyncAPI.Tests { + using System.Linq; using FluentAssertions; - using FluentAssertions.Primitives; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; using NUnit.Framework; - using System.Linq; public class AsyncApiReference_Should : TestBase { @@ -26,9 +25,10 @@ public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - deserialized.Payload.UnresolvedReference.Should().BeTrue(); + var payload = deserialized.Payload.As(); + payload.UnresolvedReference.Should().BeTrue(); - var reference = deserialized.Payload.Reference; + var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/some-resource"); reference.Id.Should().Be("/path/to/external/fragment"); reference.IsFragment.Should().BeTrue(); @@ -54,9 +54,10 @@ public void AsyncApiReference_WithFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - deserialized.Payload.UnresolvedReference.Should().BeTrue(); + var payload = deserialized.Payload.As(); + payload.UnresolvedReference.Should().BeTrue(); - var reference = deserialized.Payload.Reference; + var reference = payload.Reference; reference.Type.Should().Be(ReferenceType.Schema); reference.ExternalResource.Should().Be("/fragments/myFragment"); reference.Id.Should().BeNull(); @@ -82,7 +83,8 @@ public void AsyncApiReference_WithInternalComponentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var reference = deserialized.Payload.Reference; + var payload = deserialized.Payload.As(); + var reference = payload.Reference; reference.ExternalResource.Should().BeNull(); reference.Type.Should().Be(ReferenceType.Schema); reference.Id.Should().Be("test"); @@ -109,7 +111,8 @@ public void AsyncApiReference_WithExternalFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var reference = deserialized.Payload.Reference; + var payload = deserialized.Payload.As(); + var reference = payload.Reference; reference.ExternalResource.Should().Be("./myjsonfile.json"); reference.Id.Should().Be("/fragment"); reference.IsFragment.Should().BeTrue(); @@ -135,7 +138,8 @@ public void AsyncApiReference_WithExternalComponentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var reference = deserialized.Payload.Reference; + var payload = deserialized.Payload.As(); + var reference = payload.Reference; reference.ExternalResource.Should().Be("./someotherdocument.json"); reference.Type.Should().Be(ReferenceType.Schema); reference.Id.Should().Be("test"); @@ -268,7 +272,8 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv // Assert diagnostic.Errors.Should().BeEmpty(); - var reference = deserialized.Payload.Reference; + var payload = deserialized.Payload.As(); + var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/json.json"); reference.Id.Should().BeNull(); reference.IsExternal.Should().BeTrue(); @@ -305,7 +310,8 @@ 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"); - message.Payload.Properties.Count.Should().Be(3); + var payload = message.Payload.As(); + payload.Properties.Count.Should().Be(3); } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 830a44bc..53fc0350 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests.Models { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; using FluentAssertions; using LEGO.AsyncAPI.Models; @@ -421,7 +420,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl { new AsyncApiMessage { - Payload = new AsyncApiSchema + Payload = new AsyncApiJsonSchemaPayload { Type = SchemaType.Object, Required = new HashSet { "testB" }, @@ -510,6 +509,28 @@ public void Serialize_WithAnyOf_DoesNotWriteIf() Assert.True(!yaml.Contains("if:")); } + [Test] + public void Deserialize_BasicExample() + { + var input = + """ + title: title1 + type: integer + maximum: 42 + minimum: 10 + exclusiveMinimum: true + multipleOf: 3 + default: 15 + nullable: true + externalDocs: + url: http://example.com/externalDocs + """; + + var schema = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + + diag.Errors.Should().BeEmpty(); + schema.Should().BeEquivalentTo(AdvancedSchemaNumber); + } /// /// Regression test. /// Bug: Serializing properties multiple times - specifically Schema.Not was serialized into Not and Else. diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs new file mode 100644 index 00000000..2b5b5b0e --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -0,0 +1,388 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Tests.Models +{ + using System.Collections.Generic; + using FluentAssertions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Readers; + using NUnit.Framework; + + public class AvroSchema_Should + { + [Test] + public void Deserialize_WithMetadata_CreatesMetadata() + { + var input = + """ + { + "type": "record", + "name": "SomeEvent", + "namespace": "my.namspace.for.event", + "fields": [ + { + "name": "countryCode", + "type": "string", + "doc": "Country of the partner, (e.g. DE)" + }, + { + "name": "occurredOn", + "type": "string", + "doc": "Timestamp of when action occurred." + }, + { + "name": "partnerId", + "type": "string", + "doc": "Id of the partner" + }, + { + "name": "platformSource", + "type": "string", + "doc": "Platform source" + } + ], + "example": { + "occurredOn": "2023-11-03T09:56.582+00:00", + "partnerId": "1", + "platformSource": "Brecht", + "countryCode": "DE" + } + } + """; + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + model.Metadata.Should().HaveCount(1); + var reserialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0); + + // Assert + input.Should() + .BePlatformAgnosticEquivalentTo(reserialized); + + } + + [Test] + public void SerializeV2_SerializesCorrectly() + { + // Arrange + var expected = """ + type: record + name: User + namespace: com.example + fields: + - name: username + type: string + doc: The username of the user. + default: guest + order: ascending + - name: status + type: + type: enum + name: Status + symbols: + - ACTIVE + - INACTIVE + - BANNED + doc: The status of the user. + - name: emails + type: + type: array + items: string + doc: A list of email addresses. + - name: metadata + type: + type: map + values: string + doc: Metadata associated with the user. + - name: address + type: + type: record + name: Address + fields: + - name: street + type: string + - name: city + type: string + - name: zipcode + type: string + doc: The address of the user. + - name: profilePicture + type: + type: fixed + name: ProfilePicture + size: 256 + doc: A fixed-size profile picture. + - name: contact + type: + - 'null' + - type: record + name: PhoneNumber + fields: + - name: countryCode + type: int + - name: number + type: string + doc: 'The contact information of the user, which can be either null or a phone number.' + """; + + var schema = new AvroRecord + { + Name = "User", + Namespace = "com.example", + Fields = new List + { + new AvroField + { + Name = "username", + Type = AvroPrimitiveType.String, + Doc = "The username of the user.", + Default = new AsyncApiAny("guest"), + Order = AvroFieldOrder.Ascending, + }, + new AvroField + { + Name = "status", + Type = new AvroEnum + { + Name = "Status", + Symbols = new List { "ACTIVE", "INACTIVE", "BANNED" }, + }, + Doc = "The status of the user.", + }, + new AvroField + { + Name = "emails", + Type = new AvroArray + { + Items = AvroPrimitiveType.String, + }, + Doc = "A list of email addresses.", + }, + new AvroField + { + Name = "metadata", + Type = new AvroMap + { + Values = AvroPrimitiveType.String, + }, + Doc = "Metadata associated with the user.", + }, + new AvroField + { + Name = "address", + Type = new AvroRecord + { + Name = "Address", + Fields = new List + { + new AvroField { Name = "street", Type = AvroPrimitiveType.String }, + new AvroField { Name = "city", Type = AvroPrimitiveType.String }, + new AvroField { Name = "zipcode", Type = AvroPrimitiveType.String }, + }, + }, + Doc = "The address of the user.", + }, + new AvroField + { + Name = "profilePicture", + Type = new AvroFixed + { + Name = "ProfilePicture", + Size = 256, + }, + Doc = "A fixed-size profile picture.", + }, + new AvroField + { + Name = "contact", + Type = new AvroUnion + { + Types = new List + { + AvroPrimitiveType.Null, + new AvroRecord + { + Name = "PhoneNumber", + Fields = new List + { + new AvroField { Name = "countryCode", Type = AvroPrimitiveType.Int }, + new AvroField { Name = "number", Type = AvroPrimitiveType.String }, + }, + }, + }, + }, + Doc = "The contact information of the user, which can be either null or a phone number.", + }, + }, + }; + + // Act + var actual = schema.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + + // Assert + actual.Should() + .BePlatformAgnosticEquivalentTo(expected); + } + + [Test] + public void ReadFragment_DeserializesCorrectly() + { + // Arrange + var input = """ + type: record + name: User + namespace: com.example + fields: + - name: username + type: string + doc: The username of the user. + default: guest + order: ascending + - name: status + type: + type: enum + name: Status + symbols: + - ACTIVE + - INACTIVE + - BANNED + doc: The status of the user. + - name: emails + type: + type: array + items: string + doc: A list of email addresses. + - name: metadata + type: + type: map + values: string + doc: Metadata associated with the user. + - name: address + type: + type: record + name: Address + fields: + - name: street + type: string + - name: city + type: string + - name: zipcode + type: string + doc: The address of the user. + - name: profilePicture + type: + type: fixed + name: ProfilePicture + size: 256 + doc: A fixed-size profile picture. + - name: contact + type: + - 'null' + - type: record + name: PhoneNumber + fields: + - name: countryCode + type: int + - name: number + type: string + doc: 'The contact information of the user, which can be either null or a phone number.' + """; + + var expected = new AvroRecord + { + Name = "User", + Namespace = "com.example", + Fields = new List + { + new AvroField + { + Name = "username", + Type = AvroPrimitiveType.String, + Doc = "The username of the user.", + Default = new AsyncApiAny("guest"), + Order = AvroFieldOrder.Ascending, + }, + new AvroField + { + Name = "status", + Type = new AvroEnum + { + Name = "Status", + Symbols = new List { "ACTIVE", "INACTIVE", "BANNED" }, + }, + Doc = "The status of the user.", + }, + new AvroField + { + Name = "emails", + Type = new AvroArray + { + Items = AvroPrimitiveType.String, + }, + Doc = "A list of email addresses.", + }, + new AvroField + { + Name = "metadata", + Type = new AvroMap + { + Values = AvroPrimitiveType.String, + }, + Doc = "Metadata associated with the user.", + }, + new AvroField + { + Name = "address", + Type = new AvroRecord + { + Name = "Address", + Fields = new List + { + new AvroField { Name = "street", Type = AvroPrimitiveType.String }, + new AvroField { Name = "city", Type = AvroPrimitiveType.String }, + new AvroField { Name = "zipcode", Type = AvroPrimitiveType.String }, + }, + }, + Doc = "The address of the user.", + }, + new AvroField + { + Name = "profilePicture", + Type = new AvroFixed + { + Name = "ProfilePicture", + Size = 256, + }, + Doc = "A fixed-size profile picture.", + }, + new AvroField + { + Name = "contact", + Type = new AvroUnion + { + Types = new List + { + AvroPrimitiveType.Null, + new AvroRecord + { + Name = "PhoneNumber", + Fields = new List + { + new AvroField { Name = "countryCode", Type = AvroPrimitiveType.Int }, + new AvroField { Name = "number", Type = AvroPrimitiveType.String }, + }, + }, + }, + }, + Doc = "The contact information of the user, which can be either null or a phone number.", + }, + }, + }; + + // Act + var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + + // Assert + actual.Should() + .BeEquivalentTo(expected); + } + } +} diff --git a/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs b/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs index c364d345..69ae67e3 100644 --- a/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs @@ -2,10 +2,9 @@ namespace LEGO.AsyncAPI.Tests.Writers { + using System.IO; using LEGO.AsyncAPI.Writers; using NUnit.Framework; - using System; - using System.IO; internal class AsyncApiYamlWriterTests : TestBase { diff --git a/test/LEGO.AsyncAPI.Tests/TestBase.cs b/test/LEGO.AsyncAPI.Tests/TestBase.cs index 17309617..50e313ed 100644 --- a/test/LEGO.AsyncAPI.Tests/TestBase.cs +++ b/test/LEGO.AsyncAPI.Tests/TestBase.cs @@ -43,9 +43,9 @@ public void Log(string message) /// Attempts to find the first file that matches the name of the active unit test /// and returns it as an expected type. /// - /// The type to return + /// The type to return. /// The name of the resource file with an optional extension. - /// The result + /// The result. protected T GetTestData([CallerMemberName] string resourceName = "") { string searchPattern = string.IsNullOrWhiteSpace(Path.GetExtension(resourceName)) From 3edac3ed78e867a9f44b15c96680bc1ed86496f3 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 3 Jun 2024 16:17:10 +0200 Subject: [PATCH 03/29] feat: unmapped member handling during deserialization (#176) --- .../AsyncApiReaderSettings.cs | 6 ++ .../ParseNodes/PropertyNode.cs | 11 ++- .../UnmappedMemberHandling.cs | 20 ++++ .../V2/AsyncApiAvroSchemaDeserializer.cs | 3 +- .../AsyncApiReaderTests.cs | 93 +++++++++++++++++++ 5 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index c9534d20..f949164d 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -39,6 +39,12 @@ public class AsyncApiReaderSettings : AsyncApiSettings public ReferenceResolutionSetting ReferenceResolution { get; set; } = ReferenceResolutionSetting.ResolveInternalReferences; + /// + /// Indicates what should happen when unmapped members are encountered during deserialization. + /// Error and Warning will add an error or warning to the diagnostics object. + /// + public UnmappedMemberHandling UnmappedMemberHandling { get; set; } = UnmappedMemberHandling.Error; + /// /// Dictionary of parsers for converting extensions into strongly typed classes. /// diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs index 11120606..2313f30d 100644 --- a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs +++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs @@ -29,8 +29,7 @@ public void ParseField( IDictionary> fixedFields, IDictionary, Action> patternFields) { - var found = fixedFields.TryGetValue(this.Name, out var fixedFieldMap); - + var _ = fixedFields.TryGetValue(this.Name, out var fixedFieldMap); if (fixedFieldMap != null) { try @@ -78,8 +77,12 @@ public void ParseField( } else { - this.Context.Diagnostic.Errors.Add( - new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}")); + switch (this.Context.Settings.UnmappedMemberHandling) + { + case UnmappedMemberHandling.Error: + this.Context.Diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}")); + break; + } } } } diff --git a/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs b/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs new file mode 100644 index 00000000..b58c34c4 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs @@ -0,0 +1,20 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers +{ + /// + /// Unmapped member handling. + /// + public enum UnmappedMemberHandling + { + /// + /// Add error to diagnostics for unmapped members. + /// + Error, + + /// + /// Ignore unmapped members. + /// + Ignore, + } +} diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 013ec856..5015cef2 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Readers { using System; + using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.ParseNodes; @@ -169,7 +170,7 @@ public static AvroSchema LoadSchema(ParseNode node) mapNode.ParseFields(ref union, UnionFixedFields, UnionMetadataPatternFields); return union; default: - throw new InvalidOperationException($"Unsupported type: {type}"); + throw new AsyncApiException($"Unsupported type: {type}"); } } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index 0a4cd5e7..20716506 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -5,6 +5,7 @@ namespace LEGO.AsyncAPI.Tests using System; using System.Collections.Generic; using System.Linq; + using FluentAssertions; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; @@ -30,6 +31,7 @@ public void Read_WithExtensionParser_Parses() info: title: test version: 1.0.0 + test: 1234 contact: name: API Support url: https://www.example.com/support @@ -57,6 +59,7 @@ public void Read_WithExtensionParser_Parses() { { extensionName, valueExtensionParser }, }, + UnmappedMemberHandling = UnmappedMemberHandling.Ignore, }; var reader = new AsyncApiStringReader(settings); @@ -64,6 +67,96 @@ public void Read_WithExtensionParser_Parses() Assert.AreEqual((doc.Channels["workspace"].Extensions[extensionName] as AsyncApiAny).GetValue(), 1234); } + [Test] + public void Read_WithUnmappedMemberHandlingError_AddsError() + { + var extensionName = "x-someValue"; + var yaml = $""" + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + test: 1234 + contact: + name: API Support + url: https://www.example.com/support + email: support@example.com + channels: + workspace: + {extensionName}: onetwothreefour + """; + Func valueExtensionParser = (any) => + { + if (any.TryGetValue(out var value)) + { + if (value == "onetwothreefour") + { + return new AsyncApiAny(1234); + } + } + + return new AsyncApiAny("No value provided"); + }; + + var settings = new AsyncApiReaderSettings + { + ExtensionParsers = new Dictionary> + { + { extensionName, valueExtensionParser }, + }, + UnmappedMemberHandling = UnmappedMemberHandling.Error, + }; + + var reader = new AsyncApiStringReader(settings); + var doc = reader.Read(yaml, out var diagnostic); + diagnostic.Errors.Should().HaveCount(1); + } + + [Test] + public void Read_WithUnmappedMemberHandlingIgnore_NoErrors() + { + var extensionName = "x-someValue"; + var yaml = $""" + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + test: 1234 + contact: + name: API Support + url: https://www.example.com/support + email: support@example.com + channels: + workspace: + {extensionName}: onetwothreefour + """; + Func valueExtensionParser = (any) => + { + if (any.TryGetValue(out var value)) + { + if (value == "onetwothreefour") + { + return new AsyncApiAny(1234); + } + } + + return new AsyncApiAny("No value provided"); + }; + + var settings = new AsyncApiReaderSettings + { + ExtensionParsers = new Dictionary> + { + { extensionName, valueExtensionParser }, + }, + UnmappedMemberHandling = UnmappedMemberHandling.Ignore, + }; + + var reader = new AsyncApiStringReader(settings); + var doc = reader.Read(yaml, out var diagnostic); + diagnostic.Errors.Should().HaveCount(0); + } + [Test] public void Read_WithThrowingExtensionParser_AddsToDiagnostics() { From fdeb7db9ed084c88a956fc996763a4aa9aa0bebe Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Tue, 4 Jun 2024 13:03:19 +0200 Subject: [PATCH 04/29] chore(bindings): change bindings to be ICollection (#178) --- .../BindingsCollection.cs | 18 +++++++++--------- .../AsyncApiReaderSettings.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs index 4fd5560f..83faf695 100644 --- a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs +++ b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs @@ -45,7 +45,7 @@ public static TCollection Add( return destination; } - public static IEnumerable> All => new List> + public static ICollection> All => new List> { Pulsar, Kafka, @@ -57,18 +57,18 @@ public static TCollection Add( MQTT, }; - public static IEnumerable> Http => new List> + public static ICollection> Http => new List> { new HttpOperationBinding(), new HttpMessageBinding(), }; - public static IEnumerable> Websockets => new List> + public static ICollection> Websockets => new List> { new WebSocketsChannelBinding(), }; - public static IEnumerable> Kafka => new List> + public static ICollection> Kafka => new List> { new KafkaServerBinding(), new KafkaChannelBinding(), @@ -76,32 +76,32 @@ public static TCollection Add( new KafkaMessageBinding(), }; - public static IEnumerable> Pulsar => new List> + public static ICollection> Pulsar => new List> { new PulsarServerBinding(), new PulsarChannelBinding(), }; - public static IEnumerable> Sqs => new List> + public static ICollection> Sqs => new List> { new SqsChannelBinding(), new SqsOperationBinding(), }; - public static IEnumerable> Sns => new List> + public static ICollection> Sns => new List> { new SnsChannelBinding(), new SnsOperationBinding(), }; - public static IEnumerable> AMQP => new List> + public static ICollection> AMQP => new List> { new AMQPChannelBinding(), new AMQPOperationBinding(), new AMQPMessageBinding(), }; - public static IEnumerable> MQTT => new List> + public static ICollection> MQTT => new List> { new MQTTServerBinding(), new MQTTOperationBinding(), diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index f949164d..341a743c 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -53,7 +53,7 @@ public Dictionary> { get; set; } = new Dictionary>(); - public IEnumerable> + public ICollection> Bindings { get; set; } = new List>(); From 4089e7f2892bb9bf49c7eb057149850cfcd1221b Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 14 Jun 2024 18:22:26 +0200 Subject: [PATCH 05/29] chore: pull main into vnext (#181) Co-authored-by: lego-10-01-06[bot] <119427331+lego-10-01-06[bot]@users.noreply.github.com> --- .github/workflows/release-internal.yml | 8 ------ CHANGELOG.md | 7 +++++ Common.Build.props | 7 +++++ LICENSE => LICENSE.txt | 0 README.md | 4 +-- media/logo.png | Bin 0 -> 7975 bytes .../LEGO.AsyncAPI.Bindings.csproj | 7 +---- .../LEGO.AsyncAPI.Readers.csproj | 6 ---- .../V2/AsyncApiChannelDeserializer.cs | 2 +- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 4 --- .../Services/AsyncApiReferenceResolver.cs | 8 +++++- .../AsyncApiReaderTests.cs | 27 ++++++++++++++++++ .../Models/AsyncApiChannel_Should.cs | 21 ++++++++++++++ 13 files changed, 73 insertions(+), 28 deletions(-) rename LICENSE => LICENSE.txt (100%) create mode 100644 media/logo.png diff --git a/.github/workflows/release-internal.yml b/.github/workflows/release-internal.yml index 971be8e7..1231b9e6 100644 --- a/.github/workflows/release-internal.yml +++ b/.github/workflows/release-internal.yml @@ -1,13 +1,5 @@ name: Publish beta NuGet package on: - push: - branches: [ main ] - paths: - - 'src/LEGO.AsyncAPI/**' - - 'src/LEGO.AsyncAPI.Readers/**' - - 'src/LEGO.AsyncAPI.Bindings/**' - - ".github/workflows/release-internal.yml" - - '!**/*.md' workflow_dispatch: jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 90780c52..505c9b09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.2.1](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.0...v5.2.1) (2024-06-12) + + +### Bug Fixes + +* inline channel parameters should not deserialize as references ([#172](https://github.com/LEGO/AsyncAPI.NET/issues/172)) ([7fd3af0](https://github.com/LEGO/AsyncAPI.NET/commit/7fd3af0e6669d18e805e0bab9cafac819ea64c1d)) + # [5.2.0](https://github.com/LEGO/AsyncAPI.NET/compare/v5.1.1...v5.2.0) (2024-03-30) diff --git a/Common.Build.props b/Common.Build.props index 78aa4707..ba84a0f8 100644 --- a/Common.Build.props +++ b/Common.Build.props @@ -9,5 +9,12 @@ README.md https://github.com/LEGO/AsyncAPI.NET asyncapi .net openapi documentation + logo.png + LICENSE.txt + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index e15bf00d..fa46b5c5 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ The AsyncAPI.NET SDK contains a useful object model for the AsyncAPI specificati Install the NuGet packages: ### AsyncAPI.NET [![Nuget](https://img.shields.io/nuget/v/AsyncAPI.NET?label=AsyncAPI.NET&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET/) -[![Nuget](https://img.shields.io/nuget/vpre/AsyncAPI.NET?label=AsyncAPI.NET&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET/) +[![Nuget](https://img.shields.io/nuget/vpre/AsyncAPI.NET?label=AsyncAPI.NET-Preview&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET/) ### AsyncAPI.Readers [![Nuget](https://img.shields.io/nuget/v/AsyncAPI.NET.Readers?label=AsyncAPI.Readers&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Readers/) -[![Nuget](https://img.shields.io/nuget/vpre/AsyncAPI.NET.Readers?label=AsyncAPI.Readers&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Readers/) +[![Nuget](https://img.shields.io/nuget/vpre/AsyncAPI.NET.Readers?label=AsyncAPI.Readers-Preview&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Readers/) ### AsyncAPI.Bindings [![Nuget](https://img.shields.io/nuget/v/AsyncAPI.NET.Bindings?label=AsyncAPI.Bindings&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Bindings/) diff --git a/media/logo.png b/media/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1a25d72c6ad62640da96f1686e414483e4cce8ea GIT binary patch literal 7975 zcmbW6bx<5nxA%iff(Lg`kRXe@F7AuFI|K=C0TLuEu(&PmvVp~dCn3RIf=h6BSe(o6 z-luLo^}P45x29(L)T!!k_ssP4IdeWSn(7KT*c8|=UcA6jQk2tvZX5nNG0~r|;=|~? z&keejih|sWr+<$8juX4B}g-zuYvaFDBYd*Z?v>4;g?g-M{m4rR;A`$BaZ^ zUL!)dcL5>RwemFcYc}w(@#)TRG{8+|k`GK4*G1q*NES+&Ld)c>GOW?jsEWScg}Vw< zl9i-?v?|hz`EmUf<(u=wn5qLhQPvL&835U{6@fMUZx(j}`70K_e|-P4eni@FXN>*h z`%|3{`E^FMVWxB4yrk8vJDBNiF!Kb)9w7eJFs0|Yjkados1 zs`EFViOT3`4l^g1$`}|9+s@K#dl01s*vs*>dbmT;=%)BXs)I@g$NbeOKCvWau_^6c zg-ZbXq4`!3igPe1w~D|OP!v=Tyi2=w7=0~y7{ssm$yBD{#s*=tVxkaNq7&_NfvvsU zhBObD(+J$Q-$DHJ^S<9I3jTr?GL$c_^8mUH35zvK}n9t)wmb3q-J8__ z*cI2MR=+3`n*(iWPX0qjloNermF?J67iPQJs{iS^raY1tqM82dOSDN&8Ng_*TS7asEMOGMHyZEe>REsqttO)7fY(LZUH^Ou&`J=%1A;{6(E)G{Ob zfYZPm*PpFvoGVE=9*0l-ypq=zHr585@8M&8FB@H26&*HNkS|doc?2dx328*9?KTF*!ZsH~Z$%X(a(TZxh+-jP&&g;arj_h!D zZPCuNONseYKbB3K-|^PPW51w{VUOo6;qB+BF}=}}&L^ahQBu_2yb|b`@t)myC+z{N zOBdZ;o(jLwEfgp@GUYHXNb%6AShRLIqL^aLY}|a~b1}p;;3e^cGR>LKx*b^b*@>?< z52$fDRvst_Ze8-hOpwSQuh($kyEO%qj7*ff)DM3+=V$Xbo$a2?2?dEh#+lri(>h-C zs8C$)7ioQs!b5s`(tGM#;mUw%jnj_s`}BVgbKIOY5&hzm;3d(CvX=Sh-5-Y$8*phz z;kZ4m6Q7I4Mi-HS^TpC@@E@)(_qj!Q<_8!}QR5 z1piXx@;rko#$#<$x72cHg5%VN7#LpP5c{#``}X9{)I7zKHxxK20me7?;dG(1ROo@q zNnae7IZc(70ZDVdYHc!ZAR175=1X-qKDn8fl~!QSIPqPHgWX!BivSvvZx|U*J*Hq4 zMN{DY#aoIR zV+O=|H}PH6Dt`36PPJSXaQ%pDY$&6QrifC<*Kri$5r>hh)Gc)61m-FY41Z+xG!75ojJrdm-qtu^& zYtIy(MqW7A#3d|OxE?Uaivo+7y_jMZQJX#CctwizRA=yEGy(Bbnv)Fc_yW=6_TtrkdT&vjkiz zHa9mRM03sVw82jgh>M+~ggv?XR*8dkJ214vq-#Zk)Z{(V<<5A7#LWhg(YN>Xu1l@c z-p42yxSG<^NStQv89j02vKkUMOiA{}W9#zj7^oZ+GvDASJ1UN# zQp@1}n8smPnBd+UPksAsU|_&uFpcwK|7+LY@xk-M8CxNL9SL>ioSgbSii(FP!>Ik4 zyT&6-zp*}~oJd{hBQTDvfQUIa=;3lE&1mmY6wh&Ma{pJTumWu`pZMu7q6VKsBTsfw z-=pGO@xVRWsMxgdo?eWXyx$-=lmcTaTph0wC@<0ZZxQ+sf3~^uac0uFgyRVnu97&Tvh~@{;I_w)hK??Y_UZ4Z z8(pbGgE);E^3h(B_zA!Y3mICsa|1b!5f??Cq%KBSU_~fT1`^nDlck%m$$m69lErQg zZtJP0F9;88TBM@>km9Dtq;u5u6ryl-vf$pOc856D*fWrLxX`gYLoK$BZVtCZ`xT2c z$wGd1yZ=>1vZ=Zj*up;ixU0qwpHy(LF(>ch%|M37h#odfdb=i-za}9uYK^EZHp(Qc z6;9^tdU^~r;mr%V-EFY?jjf#8em)|Kl_~jj-7C2x`E;W((fcVl{NPdi2;jV1sK7fZ z>wv0h1EVuseW7yR?th-bf|8vWDMrWdv|}VrjC_ui%{nLayvDK;9XF@_-kY9eC!J7MoUOvq3^aGcdFcctBp$Z zm!N4~aPiqNr%O z;^Kk6l4_VWvk_MFH_U= zvb>Nd~SAXfe^fsr1;b#j8x8s?1&_k<(jtZtr2J-_x<7TTomptUplTg z%#py&dYuLGY+=E0QWVJw>H-2gwNyH)xV{n|!`%ox3C&JBb#+|RkekiY?eLD>RCh5^ zV?P`LHxSKwBE77Yghk|nKFlQZNq{(}6;(y9X06q%%gwtwJyURCfU1P6$|{(4D@#M$sgc2Mdqd!d zcQK1(#GmNe{@j0^l-e;Z8k%n@l1HA{880#=!cr-wF2KO0Di3;~4ZVj1T<#G^^wI>z zwSL$KHZKQMsh5Aop;Uet8qeUhjh%Qlz-P_aNK zi+w#9$2x`KiggD@US;GiJ3LA(d|8Ew1(=?j_lmKF9`75Srs2bd^+d%g^P9+wLNBz- zF?)PRgtxzq9J?;Jx10fFx_`F`AS=gUGbH!)ZMrrum5TL<9)0Wf3{w1|2EtEBG)rawuewEwHaFH(;RA2RKkhoe4T<#=be}#uFbOA!1brwMrsO zo${H>s}F1qXH%OK=`ylHy_0rDqHQW$XVwm>YazN2!DYX0?;0JaOceG77#?%NSl|=X zl5JhuHKSU93zBZZoNnS7!Fs#cF{9y!GXDp^+EmgNxonpEspv>1bnu z)iZmt8y&WmU}9Pegkk0&-Na87HOA%#6!zW@7eEoDs4dm*F8a5BY}B1)(XlECrRQ=imQ5> z7mf({iMH++K_Wb>rIbWd<)Ys@*yHOG%v7{w-%8hG4KAw^ zQOtB*lu)lFTC|esqS+mV=c~pbw{~DQ)qz;fhI1pf^FrTIqjCN(5E)x>yNPO8#E z<}&3@I?G#CzyzxKS}01P@txAgSm1TsoF*12tY9|ex8q3Sit;dZ_M1O>Gg1RH|Yi=q#4z88kMta5s|>~##12tku+MaLTI)K z4aS^_H;rl2QerfQ&pOXpkyRR7Uh1*D_!`B6$T^iFY+jr=Q!+GULY3TRs~;8>GTHMu zAJFBlfkj8)wjB$px>;0;Sy63mOty%)q|2gKH*Y}WWb_&&P$sHJ0g~eC^FT1 z+*0AdXg?md$36!SZO^=TC%4(g@gHaoWop?1Z%wOwkC%ByJtx%!{ds}KkrdlXmqp3J zRKRi)o-jtZ9LF0a#)3NE^OP|m1Wb(eipAlIHSqg$X13f&iu8`{dD+>IfUp@p_N-}t zEp)3CfDHj`8U}T%v~6fGty#6FB^YUgFCZ6LgWDAMqg~f)xyvPT4u->YxD%(mbsaJ` znfen~RaU+^)t%0~KYFcSX&{bQ%y*5-y-(S~?O)>$&A!r>Po#DvjrUE~3EoWA7g1J} ziG^<;dV2&^odO%x(-0r@`e(o-4&tnlKFNmZ<@rg*f96$6LU0-Oceixg&{Evx&6_py zB$NPmozD@q#JQWD2_X1h0NignBXA&IfOy#OdcD_!y8xcueCY>4j=?EQ<`!;{oL~I; zg}zO)(7Vz{$cY9#Bg?AIg_^oOJ|i#dL;O*V*8OqZSiTXBo1ss=vfXlP;7rhjfFeqd zqAQCuV33w@>1ruD`SmzpTD(`QpGTEU|z(kQW4Q@9dmeT=R9uas`br-yjU-PXn) zrYiZfNNc`nYLItG>%S!dzWP4WcKZBY18#Fig$Jvl9uu(bsF@;g#VHU>IdG1%2r?Qx zP-drNC}mSWk3Y=c%Zf|aiNoiW`K#D1HxVCep>xy}&{Mu?+qr$YKf`WHIm*0WC%9@D z)8gSj8z_?=yepV$*dJ@*kr7YX@|?X;Co6pbAm7mlvzXx<@ssa95AEX}oO&Rh@nRf| z=W!Gp>wUZ|{xV zaR(yhC}Po$t1qs^#PMImA=}o%+2)`%?BkFWaa~oA0!2;bww&ES{t`S{!Uu*0 z4UL&IjgqAr{yPwsYgNwnW=Sbhv(oh1c3v9odEe=rcz_V^G(>bN4`R$Vy1mqc@RMq^ z(1lm+y=$zj>}}aAwNQ@Wc-{~ai+Dt$VTtgg)JnM9lE@%HYNI#lCgsje2&+u=qm!gi znJXdaVc|Ksc2gab{p6>I)5ji`qNfX)c3oS963QaNI$rWpl>u1gcEpM9OIdi$d~dux zzsH~Up>o~VyM+O6L8uazZO)A0G5r{huCh#%dOGn`F`i6 z%l^7e(>N+>)+rw$&3Ry!@qCrNG$BDH)2z+zv%{U&sp&)mBU*NI$?G1!4Aw3RItMzrelt+|; zdSLIJu3*}UZBh4I&ta{wu#hP?Twgf$gp@GFO{0CGJL3}Hil{Q0{<(7JIr8~La!Ip8XJ5vlSi z-)*?+U8$SwfEhDk3o&0`ort~|w9H61nkV$$B2@(t0zl0{r&M;(UDM{x1jgeZT`5LslYc7 zbS&0{fXfpILTkIH{*g5*1RY@9d%|l{9t`Qz^W+3!Y4871RF#uOd$8kNt0rS>?Vh_S{K)G;Y&T^(Yc($5KTWy^VC*~_dW{Zw#^Q6cN z?XhVrmv}5!7qA%NZ4_6dm=w)!f3dstFR#ay=PKwgu0)~CS4BB*GJE%oQupa%Gq53OoQr?< zD=^zd^|Edv*M9{Hnb8D$Pohc?NJHEY=NmV-M#l+tWb;>JYxfyjsEI_r*0o&hwU{km z*}Y#&gNq;|xw?*5GiQb;uq>b98qZfsXbWy_K3>^X=@0e-_Upm$7$*5N>PMJ;JEbsU*my4l4YaqQ|es z2cyf01)vIE+?#vL!YA-qb{KlQ(A;>G{8xUR^qLEDy4etprX*|--!VJh{Z65rgj$VA ziS-kS!mlaz$(Lxac1S3i?Y&E6ZD23@tS|2{t=A)wzoPQzkKfEp@GY3x@uq+29QEI} z^_SU-!~)A2#i7W&L@|u$!Oq613w6OIByfS1YUIHL&wTGoi zs}CbiQ&?t$3?+U#8U}yM#x_AYNY{O|nQs0^>EL24|7Etr#kRJv;YD)cFU!=jnYu1q zfpf**zr`#iEO;5Pu)I+7gUOV8PGGl}``dnTLgpvk&70Qj{(#lDnW>l^mp^MmEWT(F zEzZ8Zu@9GXX1bW@=0T%gC^_mJ7iUZy)IPm`xa*mjIwic;?F|#C4-Rl@;2O;u!qE6~ z%g?(78r2z2W;j+7V39+Cf;?EEV*!N|QuUlTo=brWXRNye0u-*d%ztQ?5j@WIy((dH zcq@z}h*_Hu>2+ZA@Cl@XexQWZxxp$I?c*m;;ghn_143hK_Ygm{_AeO6u`a5%){76B zbY6Xdyj8xm#1U`rSY$|R)J6a2QZ5kR$jJQp6T(u?rxpwBSC?4Q+oh)(} z7klhU3a>LrlY0zSx`HASuwhucOEtLFmr9`+#M1_UlXxVmwKK|WSBw+tybZ!Ud6Q-B z^dAZYp^0jDB!jH3y?M`NZRv)bA zcMNxd3hPni1g@CiBcE3ax&&Zvw-kLZIg}_t`E9B1d>rOnp^|;Jv`k`jADjXK8bUnZ zdV@Xmz+Lf>G0IeXri3BbsKK1Kw!k4Y&+-PJTXt3M`0_ndr9n}^{jls|76A?hk<}LJ z&BW1bXHtcEr5|!gZ*%5@pdxZERxa0ZQ6<^;Wrg~6r*S2FD#`tGAngI2%vvk z8Ed2|#o4krCdsxZX3;;bm)<4BRqJq%88s>*l^c1Ib(aCf2~jg*E;fBACKrakJ3P34 zs0vC6brhvK_P)ca$E4kLm_K(mPcYfSl` z@pA$E?SvbvydE+uyOuX$kXiS&#NuL6{iW^lE@=za^A(A9!K_0Sj$ZV$7Bq; zXl@@L)G3i8WwMkygd3yjPkm*El**|@iU<-MtnC<*~h7WUvM3TA~#ku{dc2HiBUe3u>emV#;NqQ9o z$?=z&!lMwTmQ%+rz>^_+94i#XK)n=D2PIzrN*Nv+)6ugGWL{hQVb{3xs2VK%PQUTT zCnYTb(TG@c%spD=LQ3+3wU;817bVNHIjjUyJSn&&!N^}P2c z;ohYdR^M%3RvxQF|ewM^x%6N6hJ)^ZhsYq8C*JO2q6WcG>i2_N6 z%umm);(TrQjoMLtRrex8f09`8|lCf_fVR literal 0 HcmV?d00001 diff --git a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj index dc76c2ba..9c0b025e 100644 --- a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj +++ b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj @@ -20,12 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - True - \ - - + diff --git a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj index c47530c0..53071ca0 100644 --- a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj +++ b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj @@ -27,12 +27,6 @@ - - - True - \ - - diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs index cc74e0c0..1d6acae6 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs @@ -14,7 +14,7 @@ internal static partial class AsyncApiV2Deserializer { "servers", (a, n) => { a.Servers = n.CreateSimpleList(s => s.GetScalarValue()); } }, { "subscribe", (a, n) => { a.Subscribe = LoadOperation(n); } }, { "publish", (a, n) => { a.Publish = LoadOperation(n); } }, - { "parameters", (a, n) => { a.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter); } }, + { "parameters", (a, n) => { a.Parameters = n.CreateMap(LoadParameter); } }, { "bindings", (a, n) => { a.Bindings = LoadChannelBindings(n); } }, }; diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 3bfe1c9c..560c0f98 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -26,10 +26,6 @@ - - - - diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index c37c2804..af1176c3 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -225,7 +225,13 @@ private T ResolveReference(AsyncApiReference reference) try { - return this.currentDocument.ResolveReference(reference) as T; + 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) { diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index 20716506..036fae56 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs @@ -5,6 +5,7 @@ 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; @@ -428,6 +429,32 @@ public void Read_WithBasicPlusSecuritySchemeDeserializes() Assert.AreEqual("Provide your username and password for SASL/SCRAM authentication", scheme.Value.Description); } + [Test] + public void Read_WithWrongReference_AddsError() + { + var yaml = + """ + asyncapi: 2.3.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + message: + $ref: '#/components/securitySchemes/saslScram' + components: + securitySchemes: + saslScram: + type: scramSha256 + description: Provide your username and password for SASL/SCRAM authentication + """; + 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(); + } + [Test] public void Read_WithBasicPlusOAuthFlowDeserializes() { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index 19c6be71..b19cd383 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs @@ -3,15 +3,36 @@ namespace LEGO.AsyncAPI.Tests.Models { using System.Collections.Generic; + using System.Linq; using FluentAssertions; using LEGO.AsyncAPI.Bindings.Kafka; using LEGO.AsyncAPI.Bindings.WebSockets; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Readers; using NUnit.Framework; public class AsyncApiChannel_Should : TestBase { + [Test] + public void AsyncApiChannel_WithInlineParameter_DoesNotCreateReference() + { + var input = + """ + parameters: + id: + description: ids + schema: + type: string + enum: + - 08735ae0-6a1a-4578-8b4a-35aa26d15993 + - 97845c62-329c-4d87-ad24-4f611b909a10 + """; + + var channel = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _ ); + channel.Parameters.First().Value.Reference.Should().BeNull(); + } + [Test] public void AsyncApiChannel_WithWebSocketsBinding_Serializes() { From 6490f350641e396a353428e0cec27c508b285571 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 14 Jun 2024 18:30:50 +0200 Subject: [PATCH 06/29] ci: update release to release from vnext as well --- release.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.config.js b/release.config.js index 7c6ba358..7cf37e87 100644 --- a/release.config.js +++ b/release.config.js @@ -1,5 +1,5 @@ module.exports = { - branches: "main", + branches: ["main", "vnext"], plugins: [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", From d6ceb1709d6c0e083b83594897cd8c1320346cf1 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Tue, 21 May 2024 13:34:27 +0200 Subject: [PATCH 07/29] fix: inline channel parameters should not deserialize as references (#172) From a87d5113cbf993e11a3d8963106f1e827a143ea4 Mon Sep 17 00:00:00 2001 From: "lego-10-01-06[bot]" <119427331+lego-10-01-06[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:40:50 +0000 Subject: [PATCH 08/29] chore: update CHANGELOG.md From 24a1e4f15cf84ecaaa877dc5d4916caebcef1803 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Thu, 13 Jun 2024 09:01:35 +0200 Subject: [PATCH 09/29] chore: update readme (#179) From 3890562a88b1759cb97bcaf41b45bafa400a0326 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 14 Jun 2024 12:35:18 +0200 Subject: [PATCH 10/29] fix: resolving wrong reference (#180) From de45322553c21cb98ca9338bca9215f428c25560 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 14 Jun 2024 18:18:49 +0200 Subject: [PATCH 11/29] ci: remove auto preview release From 08f9fab158ede0ffd7d9096bfb5eb6e8027480ff Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 14 Jun 2024 18:30:50 +0200 Subject: [PATCH 12/29] ci: update release to release from vnext as well From f870610674ddd7fa38f344366c43db60d2e66ca9 Mon Sep 17 00:00:00 2001 From: Dec Kolakowski <51292634+dpwdec@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:51:57 +0100 Subject: [PATCH 13/29] chore: correct documentation method name (#184) Co-authored-by: dec.kolakowski --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa46b5c5..c6d0724d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ using System.IO; public class AsyncApiExternalFileSystemReader : IAsyncApiExternalReferenceReader { - public string GetExternalResource(string reference) + public string Load(string reference) { return File.ReadAllText(reference); } From 19602c9aea492bf8efe9355b0287b36f39d212d9 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Tue, 18 Jun 2024 18:13:24 +0200 Subject: [PATCH 14/29] fix: add missing avro payload resolution (#186) --- .../AsyncApiExternalReferenceResolver.cs | 12 ++- .../Models/AsyncApiReference_Should.cs | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index 2c111773..edfb1a79 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -90,10 +90,18 @@ public override void Visit(AsyncApiOperation operation) public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - if (message.Payload is AsyncApiJsonSchemaPayload) + switch (message.Payload) { - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); + case AsyncApiJsonSchemaPayload json: + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); + break; + case AsyncApiAvroSchemaPayload avro: + this.ResolveObject(message.Payload as AsyncApiAvroSchemaPayload, r => message.Payload = r); + break; + default: + break; } + 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/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 41972784..16d58b4c 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -4,6 +4,7 @@ namespace LEGO.AsyncAPI.Tests { using System.Linq; using FluentAssertions; + using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; using NUnit.Framework; @@ -313,8 +314,80 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var payload = message.Payload.As(); 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"); + } } + public class MockExternalAvroReferenceReader : IAsyncApiExternalReferenceReader + { + public string Load(string reference) + { + return + """ + { + "type": "record", + "name": "SomeEvent", + "namespace": "my.namspace.for.event", + "fields": [ + { + "name": "countryCode", + "type": "string", + "doc": "Country of the partner, (e.g. DE)" + }, + { + "name": "occurredOn", + "type": "string", + "doc": "Timestamp of when action occurred." + }, + { + "name": "partnerId", + "type": "string", + "doc": "Id of the partner" + }, + { + "name": "platformSource", + "type": "string", + "doc": "Platform source" + } + ], + "example": { + "occurredOn": "2023-11-03T09:56.582+00:00", + "partnerId": "1", + "platformSource": "Brecht", + "countryCode": "DE" + } + } + """; + } + } public class MockExternalReferenceReader : IAsyncApiExternalReferenceReader { public string Load(string reference) From a9d501d86cd837164978f0a6ecc2a9b94e1cc9df Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 12 Aug 2024 17:15:58 +0200 Subject: [PATCH 15/29] fix: JsonSchemaPayload reference resolution. --- .../AsyncApiExternalReferenceResolver.cs | 3 +- .../Services/AsyncApiReferenceResolver.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 102 ++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index edfb1a79..0f2044a1 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -93,9 +93,10 @@ public override void Visit(AsyncApiMessage message) switch (message.Payload) { case AsyncApiJsonSchemaPayload json: - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); break; case AsyncApiAvroSchemaPayload avro: + // ToFix: this might not resolve correctly. this.ResolveObject(message.Payload as AsyncApiAvroSchemaPayload, r => message.Payload = r); break; default: diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index af1176c3..3d54dae8 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -90,7 +90,7 @@ public override void Visit(AsyncApiMessage message) // #ToFix Resolve references correctly if (message.Payload is AsyncApiJsonSchemaPayload) { - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); } this.ResolveList(message.Traits); diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 4cbf1bd4..8b98727c 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1131,6 +1131,108 @@ public void SerializeV2_WithFullSpec_Serializes() .BePlatformAgnosticEquivalentTo(expected); } + [Test] + public void Read_WithAvroSchemaPayload_NoErrors() + { + // Arrange + var yaml = + """ + asyncapi: '2.6.0' + info: + title: schema-validation-test + version: '1.0.0' + description: Async API for schema validation tests + contact: + name: Test + url: https://test.test/ + + channels: + schema-validation-topic: + description: A topic to publish messages for testing Pulsar schema validation + publish: + message: + $ref: '#/components/messages/schema-validation-message' + + components: + messages: + schema-validation-message: + name: schema-validation-message + title: Message for schema validation testing that is a json object + summary: A test message is used for testing Pulsar schema validation + payload: + type: record + name: UserSignedUp + namespace: esp + doc: ESP Schema validation test + fields: + - name: userId + type: int + - name: userEmail + type: string + schemaFormat: 'application/vnd.apache.avro;version=1.9.0' + """; + + // Act + var result = new AsyncApiStringReader().Read(yaml, out var diagnostics); + + // Assert + diagnostics.Errors.Should().HaveCount(0); + result.Channels.First().Value.Publish.Message.First().Payload.As().TryGetAs(out var record).Should().BeTrue(); + record.Name.Should().Be("UserSignedUp"); + } + + [Test] + public void Read_WithJsonSchemaReference_NoErrors() + { + // Arrange + var yaml = + """ + asyncapi: '2.6.0' + info: + title: schema-validation-test + version: '1.0.0' + description: Async API for schema validation tests + contact: + name: Test + url: https://test.test/ + + channels: + schema-validation-topic: + description: A topic to publish messages for testing Pulsar schema validation + publish: + message: + $ref: '#/components/messages/schema-validation-message' + subscribe: + message: + $ref: '#/components/messages/schema-validation-message' + + components: + schemas: + schema-validation-message-payload: + type: object + properties: + content: + type: string + description: Content of the message + messages: + schema-validation-message: + name: schema-validation-message + title: Message for schema validation testing that is a json object + summary: A test message is used for testing Pulsar schema validation + payload: + $ref: '#/components/schemas/schema-validation-message-payload' + contentType: application/json + """; + + // Act + var result = new AsyncApiStringReader().Read(yaml, out var diagnostics); + + // 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); + } + [Test] public void Serialize_WithBindingReferences_SerializesDeserializes() { From 85989357524e315aa46ff30917cf76238d1024bb Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 12 Aug 2024 17:32:39 +0200 Subject: [PATCH 16/29] refactor: privatize the avro schema property and add helpers --- .../Models/AsyncApiAvroSchemaPayload.cs | 25 ++++++++++++++----- .../Models/AsyncApiMessage_Should.cs | 24 ++++++++---------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index 596115ba..0e9d1cb9 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -4,30 +4,43 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; + using System; public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload { - public AvroSchema Schema { get; set; } + private readonly AvroSchema schema; public AsyncApiAvroSchemaPayload(AvroSchema schema) { - this.Schema = schema; + this.schema = schema; } public AsyncApiAvroSchemaPayload() { + this.schema = new AvroRecord(); } public bool TryGetAs(out T schema) where T : AvroSchema { - schema = this.Schema as T; + schema = this.schema as T; return schema != null; } - public bool UnresolvedReference { get => this.Schema.UnresolvedReference; set => this.Schema.UnresolvedReference = value; } + 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 AsyncApiReference Reference { get => this.schema.Reference; set => this.schema.Reference = value; } public void SerializeV2(IAsyncApiWriter writer) { @@ -47,7 +60,7 @@ public void SerializeV2(IAsyncApiWriter writer) public void SerializeV2WithoutReference(IAsyncApiWriter writer) { - this.Schema.SerializeV2(writer); + this.schema.SerializeV2(writer); } } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index 538664a8..e37aa0d0 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -189,25 +189,23 @@ public void AsyncApiMessage_WithAvroSchemaFormat_Serializes() var message = new AsyncApiMessage(); message.SchemaFormat = "application/vnd.apache.avro"; - message.Payload = new AsyncApiAvroSchemaPayload() + var schema = new AvroRecord() { - Schema = new AvroRecord() + Name = "User", + Namespace = "com.example", + Fields = new List { - Name = "User", - Namespace = "com.example", - Fields = new List + new AvroField() { - new AvroField() - { - Name = "username", - Type = AvroPrimitiveType.String, - Doc = "The username of the user.", - Default = new AsyncApiAny("guest"), - Order = AvroFieldOrder.Ascending, - }, + Name = "username", + Type = AvroPrimitiveType.String, + Doc = "The username of the user.", + Default = new AsyncApiAny("guest"), + Order = AvroFieldOrder.Ascending, }, }, }; + message.Payload = new AsyncApiAvroSchemaPayload(schema); // Act var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); From 59fef0531e127c3965f09d1bcca1c8710a0a8a58 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 14 Oct 2024 10:43:43 +0200 Subject: [PATCH 17/29] feat: add avro logicaltypes (#195) --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 153 +++++++++++++++++- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 1 - .../Models/Avro/AvroPrimitive.cs | 4 - .../Models/Avro/LogicalTypes/AvroDate.cs | 14 ++ .../Models/Avro/LogicalTypes/AvroDecimal.cs | 26 +++ .../Models/Avro/LogicalTypes/AvroDuration.cs | 42 +++++ .../Avro/LogicalTypes/AvroLogicalType.cs | 48 ++++++ .../Avro/LogicalTypes/AvroTimeMicros.cs | 14 ++ .../Avro/LogicalTypes/AvroTimeMillis.cs | 14 ++ .../Avro/LogicalTypes/AvroTimestampMicros.cs | 14 ++ .../Avro/LogicalTypes/AvroTimestampMillis.cs | 14 ++ .../Models/Avro/LogicalTypes/AvroUUID.cs | 14 ++ .../Models/Avro/LogicalTypes/LogicalType.cs | 33 ++++ .../Models/{ => JsonSchema}/AsyncApiSchema.cs | 0 .../Writers/AsyncApiYamlWriter.cs | 12 +- .../Models/AvroSchema_Should.cs | 57 +++++++ 16 files changed, 448 insertions(+), 12 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDate.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDecimal.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMicros.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMillis.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMicros.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMillis.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroUUID.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/LogicalType.cs rename src/LEGO.AsyncAPI/Models/{ => JsonSchema}/AsyncApiSchema.cs (100%) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 5015cef2..baae8873 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -3,8 +3,10 @@ namespace LEGO.AsyncAPI.Readers { using System; + using System.Threading; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Avro.LogicalTypes; using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.ParseNodes; using LEGO.AsyncAPI.Writers; @@ -68,6 +70,60 @@ public class AsyncApiAvroSchemaDeserializer { "types", (a, n) => a.Types = n.CreateList(LoadSchema) }, }; + private static readonly FixedFieldMap DecimalFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + { "precision", (a, n) => a.Precision = int.Parse(n.GetScalarValue()) }, + { "scale", (a, n) => a.Scale = int.Parse(n.GetScalarValue()) }, + }; + + private static readonly FixedFieldMap UUIDFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap DateFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap TimeMillisFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap TimeMicrosFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap TimestampMillisFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap TimestampMicrosFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + }; + + private static readonly FixedFieldMap DurationFixedFields = new() + { + { "type", (a, n) => { } }, + { "logicalType", (a, n) => { } }, + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + { "size", (a, n) => { } }, + }; + private static readonly PatternFieldMap RecordMetadataPatternFields = new() { @@ -110,6 +166,54 @@ public class AsyncApiAvroSchemaDeserializer { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, }; + private static readonly PatternFieldMap DecimalMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap UUIDMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap DateMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap TimeMillisMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap TimeMicrosMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap TimestampMillisMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap TimestampMicrosMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + + private static readonly PatternFieldMap DurationMetadataPatternFields = + new() + { + { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() }, + }; + public static AvroSchema LoadSchema(ParseNode node) { if (node is ValueNode valueNode) @@ -141,8 +245,13 @@ public static AvroSchema LoadSchema(ParseNode node) }; } - var type = mapNode["type"]?.Value.GetScalarValue(); + var isLogicalType = mapNode["logicalType"] != null; + if (isLogicalType) + { + return LoadLogicalType(mapNode); + } + var type = mapNode["type"]?.Value.GetScalarValue(); switch (type) { case "record": @@ -177,6 +286,48 @@ public static AvroSchema LoadSchema(ParseNode node) throw new AsyncApiReaderException("Invalid node type"); } + private static AvroSchema LoadLogicalType(MapNode mapNode) + { + var type = mapNode["logicalType"]?.Value.GetScalarValue(); + switch (type) + { + case "decimal": + var @decimal = new AvroDecimal(); + mapNode.ParseFields(ref @decimal, DecimalFixedFields, DecimalMetadataPatternFields); + return @decimal; + case "uuid": + var uuid = new AvroUUID(); + mapNode.ParseFields(ref uuid, UUIDFixedFields, UUIDMetadataPatternFields); + return uuid; + case "date": + var date = new AvroDate(); + mapNode.ParseFields(ref date, DateFixedFields, DateMetadataPatternFields); + return date; + case "time-millis": + var timeMillis = new AvroTimeMillis(); + mapNode.ParseFields(ref timeMillis, TimeMillisFixedFields, TimeMillisMetadataPatternFields); + return timeMillis; + case "time-micros": + var timeMicros = new AvroTimeMicros(); + mapNode.ParseFields(ref timeMicros, TimeMicrosFixedFields, TimeMicrosMetadataPatternFields); + return timeMicros; + case "timestamp-millis": + var timestampMillis = new AvroTimestampMillis(); + mapNode.ParseFields(ref timestampMillis, TimestampMillisFixedFields, TimestampMillisMetadataPatternFields); + return timestampMillis; + case "timestamp-micros": + var timestampMicros = new AvroTimestampMicros(); + mapNode.ParseFields(ref timestampMicros, TimestampMicrosFixedFields, TimestampMicrosMetadataPatternFields); + return timestampMicros; + case "duration": + var duration = new AvroDuration(); + mapNode.ParseFields(ref duration, DurationFixedFields, DurationMetadataPatternFields); + return duration; + default: + throw new AsyncApiException($"Unsupported type: {type}"); + } + } + private static AvroField LoadField(ParseNode node) { var mapNode = node.CheckMapNode("field"); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index c66d476b..8b3028c6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using System.Linq; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index d7526344..4d12c792 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -20,10 +20,6 @@ public AvroPrimitive(AvroPrimitiveType type) this.Type = type.GetDisplayName(); } - public AvroPrimitive() - { - } - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) { writer.WriteValue(this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDate.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDate.cs new file mode 100644 index 00000000..0b01ab0c --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDate.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroDate : AvroLogicalType + { + public AvroDate() + : base(AvroPrimitiveType.Int) + { + } + + public override LogicalType LogicalType => LogicalType.Date; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDecimal.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDecimal.cs new file mode 100644 index 00000000..7c042dbb --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDecimal.cs @@ -0,0 +1,26 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + using LEGO.AsyncAPI.Writers; + + public class AvroDecimal : AvroLogicalType + { + public AvroDecimal() + : base(AvroPrimitiveType.Bytes) + { + } + + public override LogicalType LogicalType => LogicalType.Decimal; + + public int? Scale { get; set; } + + public int? Precision { get; set; } + + public override void SerializeV2Core(IAsyncApiWriter writer) + { + writer.WriteOptionalProperty("scale", this.Scale); + writer.WriteOptionalProperty("precision", this.Precision); + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs new file mode 100644 index 00000000..01469d0b --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroDuration.cs @@ -0,0 +1,42 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public class AvroDuration : AvroFixed + { + public LogicalType LogicalType { get; } = LogicalType.Duration; + + public new int Size { get; } = 12; + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteOptionalProperty("logicalType", this.LogicalType.GetDisplayName()); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalProperty("namespace", this.Namespace); + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); + writer.WriteRequiredProperty("size", this.Size); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs new file mode 100644 index 00000000..235899a1 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs @@ -0,0 +1,48 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + using System.Linq; + using LEGO.AsyncAPI.Writers; + + public abstract class AvroLogicalType : AvroPrimitive + { + protected AvroLogicalType(AvroPrimitiveType type) + : base(type) + { + } + + public abstract LogicalType LogicalType { get; } + + public virtual void SerializeV2Core(IAsyncApiWriter writer) + { + } + + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteOptionalProperty("logicalType", this.LogicalType.GetDisplayName()); + + this.SerializeV2Core(writer); + + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteAny(item.Value); + } + } + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMicros.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMicros.cs new file mode 100644 index 00000000..df7dfa59 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMicros.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroTimeMicros : AvroLogicalType + { + public AvroTimeMicros() + : base(AvroPrimitiveType.Long) + { + } + + public override LogicalType LogicalType => LogicalType.Time_Micros; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMillis.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMillis.cs new file mode 100644 index 00000000..71cbb6e2 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimeMillis.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroTimeMillis : AvroLogicalType + { + public AvroTimeMillis() + : base(AvroPrimitiveType.Int) + { + } + + public override LogicalType LogicalType => LogicalType.Time_Millis; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMicros.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMicros.cs new file mode 100644 index 00000000..295f2883 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMicros.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroTimestampMicros : AvroLogicalType + { + public AvroTimestampMicros() + : base(AvroPrimitiveType.Long) + { + } + + public override LogicalType LogicalType => LogicalType.Timestamp_Micros; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMillis.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMillis.cs new file mode 100644 index 00000000..0f03ca0c --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroTimestampMillis.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroTimestampMillis : AvroLogicalType + { + public AvroTimestampMillis() + : base(AvroPrimitiveType.Long) + { + } + + public override LogicalType LogicalType => LogicalType.Timestamp_Millis; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroUUID.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroUUID.cs new file mode 100644 index 00000000..6a7a27a0 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroUUID.cs @@ -0,0 +1,14 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + public class AvroUUID : AvroLogicalType + { + public AvroUUID() + : base(AvroPrimitiveType.String) + { + } + + public override LogicalType LogicalType => LogicalType.UUID; + } +} diff --git a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/LogicalType.cs b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/LogicalType.cs new file mode 100644 index 00000000..7db53258 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/LogicalType.cs @@ -0,0 +1,33 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes +{ + using LEGO.AsyncAPI.Attributes; + + public enum LogicalType + { + [Display("decimal")] + Decimal, + + [Display("uuid")] + UUID, + + [Display("date")] + Date, + + [Display("time-millis")] + Time_Millis, + + [Display("time-micros")] + Time_Micros, + + [Display("timestamp-millis")] + Timestamp_Millis, + + [Display("timestamp-micros")] + Timestamp_Micros, + + [Display("duration")] + Duration, + } +} diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs similarity index 100% rename from src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs rename to src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs index f7b32dd9..a278ebcd 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs @@ -1,13 +1,13 @@ // Copyright (c) The LEGO Group. All rights reserved. -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; - namespace LEGO.AsyncAPI.Writers { + using System; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + /// /// Used to conver an AsyncApi schema into a yaml document. /// diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 2b5b5b0e..0ba1e80e 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -222,6 +222,63 @@ public void SerializeV2_SerializesCorrectly() .BePlatformAgnosticEquivalentTo(expected); } + [Test] + public void SerializeV2_WithLogicalTypes_SerializesCorrectly() + { + // Arrange + var input = """ + { + "type": "array", + "items": [ + { + "type": "bytes", + "logicalType": "decimal", + "scale": 2, + "precision": 4 + }, + { + "type": "string", + "logicalType": "uuid" + }, + { + "type": "int", + "logicalType": "date" + }, + { + "type": "int", + "logicalType": "time-millis" + }, + { + "type": "long", + "logicalType": "time-micros" + }, + { + "type": "long", + "logicalType": "timestamp-millis" + }, + { + "type": "long", + "logicalType": "timestamp-micros" + }, + { + "type": "fixed", + "logicalType": "duration", + "name": "Duration", + "size": 12 + } + ] + } + """; + + // Act + 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); + + serialized.Should().BePlatformAgnosticEquivalentTo(input); + } + [Test] public void ReadFragment_DeserializesCorrectly() { From f7d96d8977cd006c308c7e80e72787eda5664841 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 25 Oct 2024 09:28:07 +0200 Subject: [PATCH 18/29] chore: add AvroValidationRule for names and symbols (#197) --- src/LEGO.AsyncAPI/Resource.Designer.cs | 18 ++++++ src/LEGO.AsyncAPI/Resource.resx | 6 ++ .../Services/AsyncApiVisitorBase.cs | 8 +++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 11 +++- .../Validation/AsyncApiValidator.cs | 4 ++ .../Validation/Rules/AsyncApiAvroRules.cs | 60 +++++++++++++++++++ .../Validation/ValidationRulesetTests.cs | 2 +- 7 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs diff --git a/src/LEGO.AsyncAPI/Resource.Designer.cs b/src/LEGO.AsyncAPI/Resource.Designer.cs index 983e6ad0..755eda9c 100644 --- a/src/LEGO.AsyncAPI/Resource.Designer.cs +++ b/src/LEGO.AsyncAPI/Resource.Designer.cs @@ -104,5 +104,23 @@ internal static string Validation_MustBeAbsoluteUrl { return ResourceManager.GetString("Validation_MustBeAbsoluteUrl", resourceCulture); } } + + /// + /// Looks up a localized string similar to '{0}' MUST match the regular expression '{1}'.. + /// + internal static string Validation_NameMustMatchRegularExpr { + get { + return ResourceManager.GetString("Validation_NameMustMatchRegularExpr", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Symbols MUST match the regular expression '{1}'.. + /// + internal static string Validation_SymbolsMustMatchRegularExpression { + get { + return ResourceManager.GetString("Validation_SymbolsMustMatchRegularExpression", resourceCulture); + } + } } } diff --git a/src/LEGO.AsyncAPI/Resource.resx b/src/LEGO.AsyncAPI/Resource.resx index fa6a1ecc..9d882464 100644 --- a/src/LEGO.AsyncAPI/Resource.resx +++ b/src/LEGO.AsyncAPI/Resource.resx @@ -132,4 +132,10 @@ The field '{0}' in '{1}' object MUST be an absolute uri. + + '{0}' MUST match the regular expression '{1}'. + + + Symbols MUST match the regular expression '{1}'. + \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs index 899731d0..c048e0f5 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -55,6 +55,14 @@ public virtual void Visit(AsyncApiDocument doc) { } + public virtual void Visit(AsyncApiJsonSchemaPayload jsonPayload) + { + } + + public virtual void Visit(AsyncApiAvroSchemaPayload avroPayload) + { + } + public virtual void Visit(IDictionary anys) { } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index a228496a..9f9c40ee 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -326,6 +326,11 @@ internal void Walk(AsyncApiParameter parameter, bool isComponent = false) this.Walk(parameter as IAsyncApiExtensible); } + internal void Walk(AsyncApiAvroSchemaPayload payload) + { + this.visitor.Visit(payload); + } + internal void Walk(AsyncApiSchema schema, bool isComponent = false) { if (schema == null || this.ProcessAsReference(schema, isComponent)) @@ -539,7 +544,11 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiSchema)payload)); } - // #ToFix Add walking for avro. + if (message.Payload is AsyncApiAvroSchemaPayload avroPayload) + { + this.Walk(AsyncApiConstants.Payload, () => this.Walk(avroPayload)); + } + 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 3f8bf392..32b36ca6 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -144,6 +144,10 @@ 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 new file mode 100644 index 00000000..b12c88b2 --- /dev/null +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs @@ -0,0 +1,60 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Validation.Rules +{ + using System.Linq; + using System.Text.RegularExpressions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Validations; + + [AsyncApiRule] + public static class AsyncApiAvroRules + { + /// + /// The key regex. + /// + public static Regex NameRegex = new Regex(@"^[A-Za-z_][A-Za-z0-9_]*$"); + + public static ValidationRule NameRegularExpression => + new ValidationRule( + (context, avroPayload) => + { + string name = null; + context.Enter("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))) + { + context.CreateError( + "SymbolsRegularExpression", + string.Format(Resource.Validation_SymbolsMustMatchRegularExpression, NameRegex.ToString())); + } + } + + if (avroPayload.TryGetAs(out var @fixed)) + { + name = @fixed.Name; + } + + if (name == null) + { + return; + } + + if (!NameRegex.IsMatch(record.Name)) + { + context.CreateError( + nameof(NameRegex), + string.Format(Resource.Validation_NameMustMatchRegularExpr, name, NameRegex.ToString())); + } + + context.Exit(); + }); + } +} diff --git a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs index 48446738..6dda6f63 100644 --- a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs @@ -35,7 +35,7 @@ public void DefaultRuleSet_PropertyReturnsTheCorrectRules() Assert.IsNotEmpty(rules); // Update the number if you add new default rule(s). - Assert.AreEqual(17, rules.Count); + Assert.AreEqual(18, rules.Count); } } } From d9f9811a88b57ff196385bfb4692e2e6daa5583e Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Thu, 7 Nov 2024 18:22:37 +0200 Subject: [PATCH 19/29] fix: update license reference (#199) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b3d2454..9f882a1a 100644 --- a/README.md +++ b/README.md @@ -141,4 +141,4 @@ This project welcomes contributions and suggestions. Do you want to contribute to the project? Find out how [here](CONTRIBUTING.md). ## License -[Modified Apache 2.0 (Section 6)](https://github.com/LEGO/AsyncAPI.NET/blob/main/LICENSE) +[Modified Apache 2.0 (Section 6)](https://github.com/LEGO/AsyncAPI.NET/blob/main/LICENSE.txt) From 219d797b4fe16b62018919c9d0b2fd3cd9247a69 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Thu, 9 Jan 2025 21:39:45 +0100 Subject: [PATCH 20/29] fix: null representation for AvroField 'default' property (#201) --- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 13 ++++++++++- .../Models/AvroSchema_Should.cs | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 6e8f9ed8..659f1a4a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -67,7 +67,18 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteOptionalProperty("name", this.Name); writer.WriteOptionalObject("type", this.Type, (w, s) => s.SerializeV2(w)); writer.WriteOptionalProperty("doc", this.Doc); - writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s)); + writer.WriteOptionalObject("default", this.Default, (w, s) => + { + if (s.TryGetValue(out string value) && value == "null") + { + w.WriteNull(); + } + else + { + w.WriteAny(s); + } + }); + if (this.Order != AvroFieldOrder.None) { writer.WriteOptionalProperty("order", this.Order.GetDisplayName()); diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 0ba1e80e..4dcce166 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -10,6 +10,29 @@ namespace LEGO.AsyncAPI.Tests.Models public class AvroSchema_Should { + [Test] + public void Serialize_WithDefaultNull_SetJsonNull() + { + var input = """ + type: record + name: User + namespace: Producer + doc: ESP Schema validation test + fields: + - name: userId + type: int + - name: userEmail + type: + - null + - string + default: null + """; + + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + var reserialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0); + reserialized.Should().Contain("default\": null"); + } + [Test] public void Deserialize_WithMetadata_CreatesMetadata() { From 571ab208d5cf65fece297654ac328b64aeb13107 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 10 Jan 2025 09:41:51 +0100 Subject: [PATCH 21/29] Update release-beta.yml --- .github/workflows/release-beta.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 9cbebb05..866db6ea 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -40,7 +40,7 @@ jobs: uses: actions/setup-dotnet@v1 - name: Build ${{ matrix.package-name }} project and pack NuGet package - run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ needs.check.outputs.version }}-beta.${{github.run_number}} + run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ needs.check.outputs.version }}-beta.${{github.run_number+99}} - name: Push generated package to GitHub Packages registry run: dotnet nuget push out-${{ matrix.package-name }}/*.nupkg -s https://api.nuget.org/v3/index.json --skip-duplicate -n --api-key ${{secrets.NUGET}} From 56df6d88979b1752204e2262c38dea6175debad1 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 10 Jan 2025 09:46:18 +0100 Subject: [PATCH 22/29] Update release-beta.yml --- .github/workflows/release-beta.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 866db6ea..b795b9c0 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -38,9 +38,15 @@ jobs: - name: Setup .NET Core @ Latest uses: actions/setup-dotnet@v1 - + + - name: run number with offset + env: + NUM: ${{ github.run_number }} + run: | + echo ::set-env name=GITHUB_RUN_NUMBER_WITH_OFFSET::$(($NUM+100)) + - run: echo ${{env.GITHUB_RUN_NUMBER_WITH_OFFSET}} - name: Build ${{ matrix.package-name }} project and pack NuGet package - run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ needs.check.outputs.version }}-beta.${{github.run_number+99}} + run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ needs.check.outputs.version }}-beta.${{env.GITHUB_RUN_NUMBER_WITH_OFFSET}} - name: Push generated package to GitHub Packages registry run: dotnet nuget push out-${{ matrix.package-name }}/*.nupkg -s https://api.nuget.org/v3/index.json --skip-duplicate -n --api-key ${{secrets.NUGET}} From 3c4861dfa812ba728830ae0ae2bed3052e931dcc Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 10 Jan 2025 09:48:09 +0100 Subject: [PATCH 23/29] Update release-beta.yml --- .github/workflows/release-beta.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index b795b9c0..7a5e69c6 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -43,7 +43,7 @@ jobs: env: NUM: ${{ github.run_number }} run: | - echo ::set-env name=GITHUB_RUN_NUMBER_WITH_OFFSET::$(($NUM+100)) + echo GITHUB_RUN_NUMBER_WITH_OFFSET=$(($NUM+100)) >> "$GITHUB_ENV" - run: echo ${{env.GITHUB_RUN_NUMBER_WITH_OFFSET}} - name: Build ${{ matrix.package-name }} project and pack NuGet package run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ needs.check.outputs.version }}-beta.${{env.GITHUB_RUN_NUMBER_WITH_OFFSET}} From 6f8d6f7277f6194c6d25b2110d37a77d6760c669 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Thu, 23 Jan 2025 13:16:26 +0100 Subject: [PATCH 24/29] chore: add channelKey validation rule (#202) --- src/LEGO.AsyncAPI/Resource.Designer.cs | 9 ++ src/LEGO.AsyncAPI/Resource.resx | 3 + .../Validation/Rules/AsyncApiDocumentRules.cs | 61 ++++++++- .../Validation/ValidationRuleTests.cs | 126 ++++++++++++++++++ 4 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs diff --git a/src/LEGO.AsyncAPI/Resource.Designer.cs b/src/LEGO.AsyncAPI/Resource.Designer.cs index 755eda9c..b28de91a 100644 --- a/src/LEGO.AsyncAPI/Resource.Designer.cs +++ b/src/LEGO.AsyncAPI/Resource.Designer.cs @@ -60,6 +60,15 @@ internal Resource() { } } + /// + /// Looks up a localized string similar to Channel signature '{0}' MUST be unique.. + /// + internal static string Validation_ChannelsMustBeUnique { + get { + return ResourceManager.GetString("Validation_ChannelsMustBeUnique", resourceCulture); + } + } + /// /// Looks up a localized string similar to The string '{0}' MUST be an email address.. /// diff --git a/src/LEGO.AsyncAPI/Resource.resx b/src/LEGO.AsyncAPI/Resource.resx index 9d882464..a97d5939 100644 --- a/src/LEGO.AsyncAPI/Resource.resx +++ b/src/LEGO.AsyncAPI/Resource.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Channel signature '{0}' MUST be unique. + The string '{0}' MUST be an email address. diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs index 27076369..d866c4df 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs @@ -2,6 +2,8 @@ namespace LEGO.AsyncAPI.Validation.Rules { + using System; + using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using LEGO.AsyncAPI.Models; @@ -10,10 +12,13 @@ namespace LEGO.AsyncAPI.Validation.Rules [AsyncApiRule] public static class AsyncApiDocumentRules { + private static TimeSpan RegexTimeout = TimeSpan.FromSeconds(1); + /// /// The key regex. /// - public static Regex KeyRegex = new Regex(@"^[a-zA-Z0-9\.\-_]+$"); + public static Regex KeyRegex = new Regex(@"^[a-zA-Z0-9\.\-_]+$", RegexOptions.None, RegexTimeout); + public static Regex ChannelKeyUriTemplateRegex = new Regex(@"^(?:(?:[^\x00-\x20""'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$", RegexOptions.IgnoreCase, RegexTimeout); public static ValidationRule DocumentRequiredFields => new ValidationRule( @@ -30,16 +35,58 @@ public static class AsyncApiDocumentRules context.Exit(); context.Enter("channels"); - if (document.Channels == null || !document.Channels.Keys.Any()) + try { - context.CreateError( - nameof(DocumentRequiredFields), - string.Format(Resource.Validation_FieldRequired, "channels", "document")); - } + // MUST have at least 1 channel + if (document.Channels == null || !document.Channels.Keys.Any()) + { + context.CreateError( + nameof(DocumentRequiredFields), + string.Format(Resource.Validation_FieldRequired, "channels", "document")); + return; + } + var hashSet = new HashSet(); + foreach (var key in document.Channels.Keys) + { + // Uri-template + if (!ChannelKeyUriTemplateRegex.IsMatch(key)) + { + context.CreateError( + "ChannelKeys", + string.Format(Resource.Validation_KeyMustMatchRegularExpr, key, "channels", KeyRegex.ToString())); + } - context.Exit(); + // Unique channel keys + var pathSignature = GetKeySignature(key); + if (!hashSet.Add(pathSignature)) + { + context.CreateError("ChannelKey", string.Format(Resource.Validation_ChannelsMustBeUnique, pathSignature)); + } + } + } + finally + { + context.Exit(); + } }); + private static string GetKeySignature(string path) + { + for (int openBrace = path.IndexOf('{'); openBrace > -1; openBrace = path.IndexOf('{', openBrace + 2)) + { + int closeBrace = path.IndexOf('}', openBrace); + + if (closeBrace < 0) + { + return path; + } + + path = path.Substring(0, openBrace + 1) + path.Substring(closeBrace); + } + + return path; + } + public static ValidationRule KeyMustBeRegularExpression => new ValidationRule( (context, document) => diff --git a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs new file mode 100644 index 00000000..5d9f7bec --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Tests.Validation +{ + using FluentAssertions; + using LEGO.AsyncAPI.Readers; + using LEGO.AsyncAPI.Validations; + using NUnit.Framework; + using System.Linq; + + public class ValidationRuleTests + { + [Test] + [TestCase("chat-{person-id}")] + public void ChannelKey_WithInvalidParameter_DiagnosticsError(string channelKey) + { + var input = + $""" + asyncapi: 2.6.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + url: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + {channelKey}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + subscribe: + operationId: sendMessage + message: + name: text + payload: + type: string + """; + + var document = new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.First().Message.Should().Be($"The key '{channelKey}' in 'channels' MUST match the regular expression '^[a-zA-Z0-9\\.\\-_]+$'."); + diagnostic.Errors.First().Pointer.Should().Be("#/channels"); + } + + [Test] + public void ChannelKey_WithNonUniqueKey_DiagnosticsError() + { + var input = + """ + asyncapi: 2.6.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + url: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + chat/{personId}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + chat/{personIdentity}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + """; + + var document = new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.First().Message.Should().Be("Channel signature 'chat/{}' MUST be unique."); + diagnostic.Errors.First().Pointer.Should().Be("#/channels"); + } + + [Test] + [TestCase("chat")] + [TestCase("/some/chat/{personId}")] + [TestCase("chat-{personId}")] + [TestCase("chat-{person_id}")] + [TestCase("chat-{person%2Did}")] + [TestCase("chat-{personId2}")] + public void ChannelKey_WithValidKey_Success(string channelKey) + { + var input = + $""" + asyncapi: 2.6.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + url: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + {channelKey}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + subscribe: + operationId: sendMessage + message: + name: text + payload: + type: string + """; + + var document = new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.Should().BeEmpty(); + } + } + +} From 366325429dc90d67507b819e04a9dfdfb9470355 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 27 Jan 2025 14:52:50 +0100 Subject: [PATCH 25/29] chore: upgrade from net6 to net8 (#204) --- Common.Build.props | 2 +- src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj | 1 + src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj | 1 + src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Common.Build.props b/Common.Build.props index ba84a0f8..0f367727 100644 --- a/Common.Build.props +++ b/Common.Build.props @@ -2,7 +2,7 @@ 10 - netstandard2.0;net6 + netstandard2.0;net8 disable The LEGO Group https://github.com/LEGO/AsyncAPI.NET diff --git a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj index 9c0b025e..2c678678 100644 --- a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj +++ b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj @@ -19,6 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj index 53071ca0..818a6305 100644 --- a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj +++ b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 99e69016..83de3df0 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + <_Parameter1>$(MSBuildProjectName).Tests From 7d39747f64e30b7adc78507f8a6de3398c17fbba Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 27 Jan 2025 14:53:19 +0100 Subject: [PATCH 26/29] refactor: rename to AsyncApiJsonSchema to remove ambiguity (#205) --- .../Http/HttpMessageBinding.cs | 2 +- .../Http/HttpOperationBinding.cs | 2 +- .../Kafka/KafkaMessageBinding.cs | 2 +- .../Kafka/KafkaOperationBinding.cs | 4 +- .../MQTT/MQTTMessageBinding.cs | 2 +- .../WebSockets/WebSocketsChannelBinding.cs | 4 +- .../AsyncApiExternalReferenceResolver.cs | 4 +- .../ParseNodes/AnyFieldMapParameter.cs | 4 +- .../ParseNodes/AnyListFieldMapParameter{T}.cs | 4 +- .../AnyMapFieldMapParameter{T,U}.cs | 4 +- .../V2/AsyncApiSchemaDeserializer.cs | 10 +- .../V2/AsyncApiV2VersionService.cs | 2 +- .../Models/AsyncApiComponents.cs | 8 +- src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 2 +- .../Models/AsyncApiMessageTrait.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs | 2 +- .../Models/AsyncApiSchemaPayload.cs | 38 ++--- .../Models/JsonSchema/AsyncApiSchema.cs | 36 ++--- .../Models/JsonSchema/FalseApiSchema.cs | 4 +- .../Services/AsyncApiReferenceResolver.cs | 4 +- .../Services/AsyncApiVisitorBase.cs | 4 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 8 +- .../Validation/AsyncApiValidator.cs | 4 +- .../AsyncApiDocumentBuilder.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 42 ++--- .../Bindings/BindingExtensions_Should.cs | 6 +- .../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, 205 insertions(+), 205 deletions(-) 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/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index 0f2044a1..a207344a 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -93,7 +93,7 @@ public override void Visit(AsyncApiMessage message) switch (message.Payload) { case AsyncApiJsonSchemaPayload json: - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); break; case AsyncApiAvroSchemaPayload avro: // ToFix: this might not resolve correctly. @@ -153,7 +153,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.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 b8e790f2..ba3130a0 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(); } @@ -215,13 +215,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); @@ -229,14 +229,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 4acc3395..d70f5bde 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/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/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 9e182265..91c6f68f 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/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs index 0931c953..40dbf4cc 100644 --- a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs +++ b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.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/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 3d54dae8..e541bd1d 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -90,7 +90,7 @@ public override void Visit(AsyncApiMessage message) // #ToFix Resolve references correctly if (message.Payload is AsyncApiJsonSchemaPayload) { - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); } this.ResolveList(message.Traits); @@ -143,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 c048e0f5..a4f7b0b1 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -153,9 +153,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 9f9c40ee..be230589 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) { @@ -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)) { @@ -541,7 +541,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)); } if (message.Payload is AsyncApiAvroSchemaPayload avroPayload) @@ -1056,7 +1056,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 32b36ca6..e501e265 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 8b98727c..355d4f87 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, @@ -1387,7 +1387,7 @@ public void Serializev2_WithBindings_Serializes() { new HttpMessageBinding { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Description = "this mah binding", }, @@ -1396,7 +1396,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/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/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 c37ada58..51815a25 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 e37aa0d0..b3705e45 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, } @@ -313,7 +313,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() var message = new AsyncApiMessage { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = "HeaderTitle", WriteOnly = true, @@ -328,16 +328,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, } @@ -376,7 +376,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() { "http", new HttpMessageBinding { - Headers = new AsyncApiSchema + Headers = new AsyncApiJsonSchema { Title = "SchemaTitle", WriteOnly = true, @@ -410,7 +410,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 61cc49ae..7e00efab 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 e2fbdaf5eb2c6274ab0f92b53681104a1623798b Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 31 Jan 2025 15:20:04 +0100 Subject: [PATCH 27/29] refactor!: centralize reference resolution and implement specific reference types. (#203) --- src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs | 30 +- src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs | 24 +- .../Sns/PrincipalObject.cs | 2 + .../Sns/PrincipalStar.cs | 2 + .../Sns/SnsOperationBinding.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 - .../AsyncApiDiagnostics.cs | 13 + .../AsyncApiExternalReferenceResolver.cs | 256 ---------- .../AsyncApiJsonDocumentReader.cs | 284 +++++++++-- .../AsyncApiReaderSettings.cs | 7 +- .../AsyncApiReferenceHostDocumentResolver.cs | 28 ++ .../AsyncApiTextReader.cs | 2 +- .../IAsyncApiExternalReferenceReader.cs | 14 - .../LEGO.AsyncAPI.Readers.csproj | 2 + .../ParseNodes/MapNode.cs | 7 +- .../ParseNodes/ValueNode.cs | 1 + src/LEGO.AsyncAPI.Readers/ParsingContext.cs | 21 + .../AsyncApiRemoteReferenceCollector.cs | 47 ++ .../Services/DefaultStreamLoader.cs | 62 +-- .../V2/AsyncApiAvroSchemaDeserializer.cs | 12 +- .../V2/AsyncApiChannelBindingDeserializer.cs | 2 +- .../V2/AsyncApiChannelDeserializer.cs | 2 +- .../V2/AsyncApiComponentsDeserializer.cs | 26 +- .../V2/AsyncApiCorrelationIdDeserializer.cs | 2 +- .../V2/AsyncApiMessageBindingDeserializer.cs | 6 +- .../V2/AsyncApiMessageDeserializer.cs | 6 +- .../V2/AsyncApiMessageTraitDeserializer.cs | 2 +- .../AsyncApiOperationBindingDeserializer.cs | 6 +- .../V2/AsyncApiOperationTraitDeserializer.cs | 2 +- .../V2/AsyncApiParameterDeserializer.cs | 2 +- .../V2/AsyncApiSchemaDeserializer.cs | 6 +- ...AsyncApiSecurityRequirementDeserializer.cs | 12 +- .../V2/AsyncApiSecuritySchemeDeserializer.cs | 2 +- .../V2/AsyncApiServerBindingDeserializer.cs | 2 +- .../V2/AsyncApiServerDeserializer.cs | 2 +- .../V2/AsyncApiServerVariableDeserializer.cs | 2 +- .../V2/AsyncApiV2VersionService.cs | 107 +---- src/LEGO.AsyncAPI.Readers/YamlConverter.cs | 26 +- src/LEGO.AsyncAPI/AsyncApiWorkspace.cs | 192 ++++++++ src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 1 + .../Models/AsyncApiAvroSchemaPayload.cs | 66 --- src/LEGO.AsyncAPI/Models/AsyncApiBinding.cs | 11 - .../Models/AsyncApiBindings{TBinding}.cs | 104 +++- src/LEGO.AsyncAPI/Models/AsyncApiChannel.cs | 44 +- .../Models/AsyncApiComponents.cs | 94 ++-- .../Models/AsyncApiCorrelationId.cs | 32 +- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 106 +---- src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 60 +-- .../Models/AsyncApiMessageTrait.cs | 55 +-- .../Models/AsyncApiOperationTrait.cs | 40 +- src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs | 36 +- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 106 +++-- .../AsyncApiReferenceEqualityComparer.cs | 4 +- .../Models/AsyncApiSchemaPayload.cs | 127 ----- .../Models/AsyncApiSecurityRequirement.cs | 4 +- .../Models/AsyncApiSecurityScheme.cs | 93 +--- src/LEGO.AsyncAPI/Models/AsyncApiServer.cs | 47 +- .../Models/AsyncApiServerVariable.cs | 34 +- .../Models/Avro/AsyncApiAvroSchema.cs | 45 ++ src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 6 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 4 +- .../Models/Avro/AvroPrimitive.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 46 -- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 6 +- .../Models/Avro/LogicalTypes/AvroDuration.cs | 2 +- .../Avro/LogicalTypes/AvroLogicalType.cs | 2 +- .../Models/Interfaces/IAsyncApiPayload.cs | 2 +- .../Interfaces/IAsyncApiReferenceable.cs | 9 +- ...syncApiSchema.cs => AsyncApiJsonSchema.cs} | 146 ++---- .../Models/MessagePayloadExtensions.cs | 30 ++ .../References/AsyncApiAvroSchemaReference.cs | 80 ++++ .../AsyncApiBindingsReference{TBinding}.cs | 135 ++++++ .../References/AsyncApiChannelReference.cs | 59 +++ .../AsyncApiCorrelationIdReference.cs | 54 +++ .../References/AsyncApiJsonSchemaReference.cs | 319 +++++++++++++ .../References/AsyncApiMessageReference.cs | 80 ++++ .../AsyncApiMessageTraitReference.cs | 76 +++ .../AsyncApiOperationTraitReference.cs | 62 +++ .../References/AsyncApiParameterReference.cs | 56 +++ .../AsyncApiSecuritySchemeReference.cs | 67 +++ .../References/AsyncApiServerReference.cs | 66 +++ .../AsyncApiServerVariableReference.cs | 57 +++ .../Services/AsyncApiReferenceResolver.cs | 248 ---------- .../Services/AsyncApiVisitorBase.cs | 15 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 101 ++-- .../Validation/AsyncApiValidator.cs | 10 +- .../Validation/Rules/AsyncApiAvroRules.cs | 60 +-- .../Validation/Rules/AsyncApiDocumentRules.cs | 2 +- .../Writers/AsyncApiWriterBase.cs | 3 + .../Writers/AsyncApiWriterSettings.cs | 15 +- .../Writers/AsyncApiYamlWriter.cs | 10 - src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs | 4 + .../AsyncApiDocumentV2Tests.cs | 289 +++-------- .../AsyncApiLicenseTests.cs | 2 +- .../AsyncApiReaderTests.cs | 33 +- .../WebSockets/WebSocketBindings_Should.cs | 2 +- .../LEGO.AsyncAPI.Tests.csproj | 3 +- .../Models/AsyncApiAnyTests.cs | 10 +- .../Models/AsyncApiChannel_Should.cs | 4 +- .../Models/AsyncApiMessage_Should.cs | 20 +- .../Models/AsyncApiReference_Should.cs | 447 ++++++++++++------ .../Models/AsyncApiSchema_Should.cs | 20 +- .../AsyncApiSecurityRequirement_Should.cs | 2 +- .../Models/AsyncApiServer_Should.cs | 11 +- .../Models/AvroSchema_Should.cs | 12 +- .../Validation/ValidationRuleTests.cs | 1 - 115 files changed, 2834 insertions(+), 2262 deletions(-) delete mode 100644 src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs create mode 100644 src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs delete mode 100644 src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs create mode 100644 src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs create mode 100644 src/LEGO.AsyncAPI/AsyncApiWorkspace.cs delete mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs delete mode 100644 src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs delete mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs rename src/LEGO.AsyncAPI/Models/JsonSchema/{AsyncApiSchema.cs => AsyncApiJsonSchema.cs} (78%) create mode 100644 src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiAvroSchemaReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiBindingsReference{TBinding}.cs 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/AsyncApiJsonSchemaReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.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/AsyncApiServerReference.cs create mode 100644 src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs delete mode 100644 src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.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/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.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/AsyncApiDiagnostics.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs index faceb39e..f1412e25 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiDiagnostics.cs @@ -13,5 +13,18 @@ public class AsyncApiDiagnostic : IDiagnostic public IList Warnings { get; set; } = new List(); public AsyncApiVersion SpecificationVersion { get; set; } + + public void Append(AsyncApiDiagnostic diagnosticToAdd) + { + foreach (var error in diagnosticToAdd.Errors) + { + this.Errors.Add(new(error.Pointer, error.Message)); + } + + foreach (var warning in diagnosticToAdd.Warnings) + { + this.Warnings.Add(new(warning.Pointer, warning.Message)); + } + } } } diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs deleted file mode 100644 index a207344a..00000000 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ /dev/null @@ -1,256 +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); - switch (message.Payload) - { - case AsyncApiJsonSchemaPayload json: - this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = new AsyncApiJsonSchemaPayload(r)); - break; - case AsyncApiAvroSchemaPayload avro: - // ToFix: this might not resolve correctly. - this.ResolveObject(message.Payload as AsyncApiAvroSchemaPayload, r => message.Payload = r); - break; - default: - break; - } - - 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() - { - 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..4512fb44 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -2,18 +2,22 @@ namespace LEGO.AsyncAPI.Readers { - using System.Collections.Generic; + using System; + using System.IO; using System.Linq; 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.Interface; + 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. @@ -21,6 +25,7 @@ namespace LEGO.AsyncAPI.Readers internal class AsyncApiJsonDocumentReader : IAsyncApiReader { private readonly AsyncApiReaderSettings settings; + private ParsingContext context; /// /// Initializes a new instance of the class. @@ -40,7 +45,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), @@ -52,8 +57,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) @@ -81,17 +85,21 @@ 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); - this.ResolveReferences(diagnostic, document); + document = this.context.Parse(input); + await this.ResolveReferencesAsync(diagnostic, document); } catch (AsyncApiException ex) { @@ -131,7 +139,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), @@ -144,7 +152,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) { @@ -164,50 +172,256 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi return (T)element; } - private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private async Task ResolveReferencesAsync(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; + 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); + await this.ResolveReferencesAsync(diagnostic, component); + } + } + + private void ResolveReferences(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 = 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 void ResolveAllReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private IAsyncApiSerializable ResolveExternalReference(AsyncApiDiagnostic diagnostic, IAsyncApiReferenceable reference) { - this.ResolveInternalReferences(diagnostic, document); - this.ResolveExternalReferences(diagnostic, document); + if (reference is null) + { + throw new ArgumentNullException(nameof(reference)); + } + + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); + try + { + 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.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 void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private async Task ResolveExternalReferenceAsync(AsyncApiDiagnostic diagnostic, IAsyncApiReferenceable reference) { - var errors = new List(); + if (reference is null) + { + throw new ArgumentNullException(nameof(reference)); + } - var reader = new AsyncApiStringReader(this.settings); - errors.AddRange(document.ResolveReferences()); + var loader = this.settings.ExternalReferenceLoader ??= new DefaultStreamLoader(); + 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); + } - foreach (var item in errors) + return this.ResolveStreamReference(stream, reference, diagnostic); + } + catch (AsyncApiException ex) { - diagnostic.Errors.Add(item); + diagnostic.Errors.Add(new AsyncApiError(ex)); + return null; } } - private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) + private JsonNode ReadToJson(Stream stream) { - var resolver = new AsyncApiExternalReferenceResolver(document, this.settings); - var walker = new AsyncApiWalker(resolver); - walker.Walk(document); + 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 ResolveStreamReference(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}'")); + return null; + } - foreach (var error in resolver.Errors) + if (reference.Reference.IsFragment) { - diagnostic.Errors.Add(error); + var pointer = JsonPointer.Parse(reference.Reference.FragmentId); + 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; + } } + + AsyncApiDiagnostic fragmentDiagnostic = new AsyncApiDiagnostic(); + IAsyncApiSerializable result = null; + switch (reference.Reference.Type) + { + case ReferenceType.Schema: + 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; + case ReferenceType.Channel: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.Message: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.SecurityScheme: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.Parameter: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.CorrelationId: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.OperationTrait: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.MessageTrait: + result = this.ReadFragment(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.ServerBindings: + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.ChannelBindings: + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.OperationBindings: + result = this.ReadFragment>(json, AsyncApiVersion.AsyncApi2_0, out fragmentDiagnostic); + break; + case ReferenceType.MessageBindings: + 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; } } -} \ No newline at end of file +} diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index 341a743c..890b83e6 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -72,6 +72,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/AsyncApiReferenceHostDocumentResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs new file mode 100644 index 00000000..362b7011 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReferenceHostDocumentResolver.cs @@ -0,0 +1,28 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers +{ + using System.Collections.Generic; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Services; + + internal class AsyncApiReferenceWorkspaceResolver : AsyncApiVisitorBase + { + private AsyncApiWorkspace workspace; + + public AsyncApiReferenceWorkspaceResolver( + AsyncApiWorkspace workspace) + { + this.workspace = workspace; + } + + public override void Visit(IAsyncApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.Workspace = this.workspace; + } + } + } +} \ 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/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/LEGO.AsyncAPI.Readers.csproj b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj index 818a6305..90a0d82f 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/MapNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs index 63bea6fc..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 @@ -185,7 +181,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/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/ParsingContext.cs b/src/LEGO.AsyncAPI.Readers/ParsingContext.cs index 9bcc051b..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; @@ -44,6 +46,8 @@ internal Dictionary> ExtensionPars /// public AsyncApiReaderSettings Settings { get; } + public AsyncApiWorkspace Workspace { get; } + ///// ///// Initializes a new instance of the class. ///// @@ -63,6 +67,7 @@ public ParsingContext(AsyncApiDiagnostic diagnostic, AsyncApiReaderSettings sett { this.Diagnostic = diagnostic; this.Settings = settings; + this.Workspace = new AsyncApiWorkspace(); } internal AsyncApiDocument Parse(JsonNode jsonNode) @@ -78,6 +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); + + // 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; @@ -88,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 new file mode 100644 index 00000000..4094fc6a --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/Services/AsyncApiRemoteReferenceCollector.cs @@ -0,0 +1,47 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers.Services +{ + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Services; + + internal class AsyncApiReferenceCollector : AsyncApiVisitorBase + { + private readonly List references = new(); + private AsyncApiWorkspace workspace; + + public AsyncApiReferenceCollector( + AsyncApiWorkspace workspace) + { + this.workspace = workspace; + } + /// + /// List of all external references collected from AsyncApiDocument. + /// + public IEnumerable References + { + get + { + return this.references; + } + } + + /// + /// Collect reference for each reference. + /// + /// + public override void Visit(IAsyncApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + if (referenceable.Reference.Workspace == null) + { + referenceable.Reference.Workspace = this.workspace; + } + + this.references.Add(referenceable); + } + } + } +} diff --git a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs index 6e506730..3d949a23 100644 --- a/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.cs +++ b/src/LEGO.AsyncAPI.Readers/Services/DefaultStreamLoader.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 { @@ -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) { - var absoluteUri = 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(absoluteUri.AbsolutePath); - case "http": - case "https": - return this.httpClient.GetStreamAsync(absoluteUri).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) { - var absoluteUri = new Uri(this.baseUrl, uri); - - switch (absoluteUri.Scheme) + try { - case "file": - return File.OpenRead(absoluteUri.AbsolutePath); - case "http": - case "https": - return await this.httpClient.GetStreamAsync(absoluteUri); - 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.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index baae8873..348005e0 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; @@ -214,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) { @@ -238,11 +236,7 @@ public static AvroSchema 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; @@ -286,7 +280,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/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/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 df13308a..75c50873 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs @@ -10,19 +10,19 @@ 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) }, - { "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) }, - { "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) }, - { "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) }, + { "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) }, + { "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.CreateMap(LoadOperationTrait) }, + { "messageTraits", (a, n) => a.MessageTraits = n.CreateMap(LoadMessageTrait) }, + { "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/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/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/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 2670d88a..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)}"); @@ -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/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/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/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/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/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.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/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/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.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/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.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index d70f5bde..6cbca7ed 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 { @@ -36,16 +35,21 @@ 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, [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, }; /// @@ -62,78 +66,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; - if (reference.StartsWith("/")) - { - asyncApiReference.IsFragment = true; - } - - asyncApiReference.ExternalResource = segments[0]; - - return asyncApiReference; + return new AsyncApiReference(reference, type); } - else if (segments.Length == 2) + catch (AsyncApiException ex) { - // Local reference - if (reference.StartsWith("#")) - { - try - { - return this.ParseReference(segments[1]); - } - catch (AsyncApiException ex) - { - this.Diagnostic.Errors.Add(new AsyncApiError(ex)); - return null; - } - } - - var id = segments[1]; - var asyncApiReference = new AsyncApiReference(); - 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) @@ -146,27 +87,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.Readers/YamlConverter.cs b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs index 648ee50a..942bc28f 100644 --- a/src/LEGO.AsyncAPI.Readers/YamlConverter.cs +++ b/src/LEGO.AsyncAPI.Readers/YamlConverter.cs @@ -10,55 +10,55 @@ 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) - : 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 new file mode 100644 index 00000000..5d581e22 --- /dev/null +++ b/src/LEGO.AsyncAPI/AsyncApiWorkspace.cs @@ -0,0 +1,192 @@ +// 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 artifactsRegistry = new(); + private readonly Dictionary resolvedReferenceRegistry = new(); + + public void RegisterComponents(AsyncApiDocument document) + { + 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); + this.RegisterComponent(item.Key, 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); + } + } + + public bool RegisterComponent(string location, T component) + { + var uri = this.ToLocationUrl(location); + if (component is IAsyncApiSerializable referenceable) + { + if (!this.resolvedReferenceRegistry.ContainsKey(uri)) + { + this.resolvedReferenceRegistry[uri] = referenceable; + return true; + } + } + + if (component is Stream stream) + { + if (!this.artifactsRegistry.ContainsKey(uri)) + { + this.artifactsRegistry[uri] = stream; + } + return true; + } + + return false; + } + + public bool Contains(string location) + { + var key = this.ToLocationUrl(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 + { + var uri = this.ToLocationUrl(location); + if (this.resolvedReferenceRegistry.TryGetValue(uri, out var referenceableValue)) + { + return referenceableValue as T; + } + + if (this.artifactsRegistry.TryGetValue(uri, out var stream)) + { + stream.Position = 0; + return (T)(object)stream; + } + + return default; + } + + private Uri ToLocationUrl(string location) + { + return new(location, UriKind.RelativeOrAbsolute); + } + } +} diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 83de3df0..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 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/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/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 b64f5a66..0d00a9d9 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiComponents.cs @@ -97,21 +97,21 @@ 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(); - 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.Id); + 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.Id == key) + if (component is AsyncApiJsonSchemaReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -148,11 +146,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 { @@ -166,11 +162,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 { @@ -184,11 +178,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 { @@ -202,11 +194,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 { @@ -220,11 +210,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 { @@ -238,11 +226,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 { @@ -256,11 +242,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 { @@ -274,11 +258,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.OperationTraits, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.OperationTrait && - component.Reference.Id == key) + if (component is AsyncApiOperationTraitReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -292,11 +274,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.MessageTraits, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.MessageTrait && - component.Reference.Id == key) + if (component is AsyncApiMessageTraitReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -310,11 +290,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.ServerBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.ServerBindings && - component.Reference.Id == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -328,11 +306,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.ChannelBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.ChannelBindings && - component.Reference.Id == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -346,11 +322,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.OperationBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.OperationBindings && - component.Reference.Id == key) + if (component is AsyncApiBindingsReference reference) { - component.SerializeV2WithoutReference(w); + reference.SerializeV2(w); } else { @@ -364,11 +338,9 @@ public void SerializeV2(IAsyncApiWriter writer) this.MessageBindings, (w, key, component) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.MessageBindings && - component.Reference.Id == key) + if (component is AsyncApiBindingsReference 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 5be55202..5d90e7a8 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -4,10 +4,8 @@ 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; /// /// This is the root document object for the API specification. It combines resource listing and API declaration together into one document. @@ -73,10 +71,7 @@ public void SerializeV2(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - if (writer.GetSettings().InlineReferences) - { - this.ResolveReferences(); - } + writer.Workspace.RegisterComponents(this); writer.WriteStartObject(); @@ -90,37 +85,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)); @@ -136,76 +107,5 @@ 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, IAsyncApiReferenceable - { - return this.ResolveReference(reference) as T; - } - - 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/AsyncApiMessage.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs index e0cc6776..6853222d 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs @@ -1,4 +1,4 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Models { @@ -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/AsyncApiMessageTrait.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessageTrait.cs index c14a9a24..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,79 +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 IDictionary Extensions { get; set; } = new Dictionary(); - - /// - public bool UnresolvedReference { get; set; } + public virtual IList Examples { get; set; } = new List(); /// - 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) { 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/AsyncApiParameter.cs b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs index 4c3ef683..1db08626 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiParameter.cs @@ -1,4 +1,4 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Models { @@ -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/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 9475310e..38386b7b 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Models { using System; + using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -11,13 +12,81 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiReference : IAsyncApiSerializable { + private string originalString; + + public AsyncApiReference(string reference, ReferenceType? type) + { + if (string.IsNullOrWhiteSpace(reference)) + { + 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 = ReferenceType.SecurityScheme; + this.FragmentId = reference; + return; + } + + this.Type = type; + this.ExternalResource = segments[0]; + + return; + } + else if (segments.Length == 2) + { + if (reference.StartsWith("#")) // is local reference + { + var localSegments = reference.Split('/'); + + if (localSegments.Length == 4) + { + if (localSegments[1] == "components") // is local components reference + { + var referencedType = localSegments[2].GetEnumFromDisplayName(); + if (type == null || type == ReferenceType.None) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new AsyncApiException("Referenced type mismatch"); + } + } + } + } + } + else + { + this.ExternalResource = segments[0]; + } + + this.Type = type; + this.FragmentId = segments[1]; + + 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. @@ -27,17 +96,17 @@ public class AsyncApiReference : IAsyncApiSerializable /// /// 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. /// - 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. /// - public bool IsFragment { get; set; } = false; + public bool IsFragment => this.FragmentId != null; /// /// Gets a flag indicating whether this reference is an external reference. @@ -51,22 +120,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; } } @@ -97,17 +151,7 @@ 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.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/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs deleted file mode 100644 index 91c6f68f..00000000 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs +++ /dev/null @@ -1,127 +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 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); - } - } -} diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSecurityRequirement.cs index 475497d2..95e93a89 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. @@ -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/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/AsyncApiServer.cs b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs index 5d4fa071..396b65d9 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiServer.cs @@ -2,40 +2,35 @@ 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 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 +38,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/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/Avro/AsyncApiAvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs new file mode 100644 index 00000000..3e1fa291 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AsyncApiAvroSchema.cs @@ -0,0 +1,45 @@ +// 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 abstract class AsyncApiAvroSchema : IAsyncApiSerializable, IAsyncApiMessagePayload + { + public abstract string Type { get; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public abstract IDictionary Metadata { get; set; } + + public static implicit operator AsyncApiAvroSchema(AvroPrimitiveType type) + { + return new AvroPrimitive(type); + } + + public abstract void SerializeV2(IAsyncApiWriter 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 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 0958fa3d..d00e6f79 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -6,21 +6,21 @@ 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. /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(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 dbfbb627..ced40502 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"; @@ -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 SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); 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 241e8c1e..e50ec714 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"; @@ -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 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 8b3028c6..ba762d69 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"; @@ -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 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 4d12c792..fe97023a 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; } @@ -20,7 +20,7 @@ public AvroPrimitive(AvroPrimitiveType type) this.Type = type.GetDisplayName(); } - public override void SerializeV2WithoutReference(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 04642f5c..bbb085a9 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"; @@ -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 SerializeV2(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 deleted file mode 100644 index fce1df2d..00000000 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ /dev/null @@ -1,46 +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 abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable - { - public abstract string Type { get; } - - /// - /// A map of properties not in the schema, but added as additional metadata. - /// - public abstract IDictionary Metadata { get; set; } - - public bool UnresolvedReference { get; set; } - - public AsyncApiReference Reference { get; set; } - - public static implicit operator AvroSchema(AvroPrimitiveType type) - { - return new AvroPrimitive(type); - } - - 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 abstract void SerializeV2WithoutReference(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..df5832e1 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -6,21 +6,21 @@ 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. /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2WithoutReference(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 01469d0b..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 SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2(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..cb0f5b3c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/LogicalTypes/AvroLogicalType.cs @@ -18,7 +18,7 @@ public virtual void SerializeV2Core(IAsyncApiWriter writer) { } - public override void SerializeV2WithoutReference(IAsyncApiWriter writer) + public override void SerializeV2(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/Interfaces/IAsyncApiReferenceable.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs index 9f0bc64c..b1de15ae 100644 --- a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs +++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiReferenceable.cs @@ -2,23 +2,16 @@ namespace LEGO.AsyncAPI.Models.Interfaces { - using LEGO.AsyncAPI.Writers; - 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. /// 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/JsonSchema/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs similarity index 78% rename from src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.cs rename to src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiJsonSchema.cs index 40dbf4cc..3d36eaf1 100644 --- a/src/LEGO.AsyncAPI/Models/JsonSchema/AsyncApiSchema.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, IAsyncApiMessagePayload { /// /// 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/MessagePayloadExtensions.cs b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs new file mode 100644 index 00000000..069869b6 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/MessagePayloadExtensions.cs @@ -0,0 +1,30 @@ +// 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 T As(this IAsyncApiMessagePayload payload) + where T : class, IAsyncApiMessagePayload + { + return payload as T; + } + + public static bool Is(this IAsyncApiMessagePayload payload) + where T : class, IAsyncApiMessagePayload + { + return payload 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..decfc0c6 --- /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 (this.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/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/Models/References/AsyncApiChannelReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs new file mode 100644 index 00000000..437c4e50 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiChannelReference.cs @@ -0,0 +1,59 @@ +// 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 AsyncApiChannelReference : AsyncApiChannel, IAsyncApiReferenceable + { + private AsyncApiChannel target; + + private AsyncApiChannel Target + { + get + { + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.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.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 new file mode 100644 index 00000000..89784131 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiCorrelationIdReference.cs @@ -0,0 +1,54 @@ +// 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 correlation ID this application MAY use. + /// + public class AsyncApiCorrelationIdReference : AsyncApiCorrelationId, IAsyncApiReferenceable + { + private AsyncApiCorrelationId target; + + private AsyncApiCorrelationId Target + { + get + { + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.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.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 new file mode 100644 index 00000000..a7211559 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiJsonSchemaReference.cs @@ -0,0 +1,319 @@ +// 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.Workspace?.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; + } + + this.Reference.Workspace = writer.Workspace; + // 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/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs new file mode 100644 index 00000000..05aa8ed8 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -0,0 +1,80 @@ +// 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 this application MAY use. + /// + public class AsyncApiMessageReference : AsyncApiMessage, IAsyncApiReferenceable + { + private AsyncApiMessage target; + + private AsyncApiMessage Target + { + get + { + this.target ??= this.Reference.Workspace?.ResolveReference(this.Reference.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.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 new file mode 100644 index 00000000..118ad4b9 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageTraitReference.cs @@ -0,0 +1,76 @@ +// 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.Workspace?.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.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 new file mode 100644 index 00000000..36bb3bd9 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiOperationTraitReference.cs @@ -0,0 +1,62 @@ +// 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.Workspace?.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.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 new file mode 100644 index 00000000..1c570ca0 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiParameterReference.cs @@ -0,0 +1,56 @@ +// 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 parameter this application MAY use. + /// + public class AsyncApiParameterReference : AsyncApiParameter, IAsyncApiReferenceable + { + private AsyncApiParameter target; + + private AsyncApiParameter Target + { + get + { + this.target ??= this.Reference.Workspace?.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.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 new file mode 100644 index 00000000..7e8440ef --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiSecuritySchemeReference.cs @@ -0,0 +1,67 @@ +// 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 security scheme this application MAY use. + /// + public class AsyncApiSecuritySchemeReference : AsyncApiSecurityScheme, IAsyncApiReferenceable + { + private AsyncApiSecurityScheme target; + + private AsyncApiSecurityScheme Target + { + get + { + this.target ??= this.Reference.Workspace?.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.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 new file mode 100644 index 00000000..1ef0d510 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerReference.cs @@ -0,0 +1,66 @@ +// 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 server this application MAY connect to. + /// + public class AsyncApiServerReference : AsyncApiServer, IAsyncApiReferenceable + { + private AsyncApiServer target; + + private AsyncApiServer Target + { + get + { + this.target ??= this.Reference.Workspace?.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 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.Reference.Workspace = writer.Workspace; + this.Target.SerializeV2(writer); + } + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs new file mode 100644 index 00000000..c5bb968d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiServerVariableReference.cs @@ -0,0 +1,57 @@ +// 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 server variable this application MAY use. + /// + public class AsyncApiServerVariableReference : AsyncApiServerVariable, IAsyncApiReferenceable + { + private AsyncApiServerVariable target; + + private AsyncApiServerVariable Target + { + get + { + this.target ??= this.Reference.Workspace?.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.Reference.Workspace = writer.Workspace; + this.Target.SerializeV2(writer); + } + } + } +} diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs deleted file mode 100644 index e541bd1d..00000000 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ /dev/null @@ -1,248 +0,0 @@ -// 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. - /// - 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 = new AsyncApiJsonSchemaPayload(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); - } - } -} \ No newline at end of file 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 be230589..2ee2226b 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -1,4 +1,4 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Services { @@ -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,22 +271,21 @@ internal void Walk(AsyncApiExternalDocumentation externalDocs) internal void Walk(AsyncApiChannel channel, bool isComponent = false) { - if (channel == null || this.ProcessAsReference(channel, isComponent)) + if (channel == null) { return; } - - this.visitor.Visit(channel); - - if (channel != null) + if (channel is AsyncApiChannelReference) { - 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 IAsyncApiReferenceable); + return; } + 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); } @@ -311,8 +311,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; } @@ -326,15 +327,41 @@ 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) + { + if (schema is AsyncApiAvroSchemaReference reference) + { + this.Walk(reference as IAsyncApiReferenceable); + return; + } + + this.visitor.Visit(schema); } internal void Walk(AsyncApiJsonSchema schema, bool isComponent = false) { - if (schema == null || this.ProcessAsReference(schema, isComponent)) + if (schema == null) + { + return; + } + + if (schema is AsyncApiJsonSchemaReference reference) { + this.Walk(reference as IAsyncApiReferenceable); return; } @@ -510,8 +537,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; } @@ -529,8 +557,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; } @@ -539,16 +568,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)); @@ -581,8 +601,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; } @@ -603,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; } @@ -632,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; } @@ -661,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; } @@ -690,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; } @@ -775,8 +800,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; } @@ -836,8 +862,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; } @@ -875,6 +902,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); } @@ -1057,6 +1089,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/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index e501e265..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 . /// @@ -186,7 +186,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/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/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/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterBase.cs index 7cc6ee02..d6d6b750 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 AsyncApiWorkspace Workspace { get; set; } = new AsyncApiWorkspace(); + /// /// Write start object. /// diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs index 2b663bb2..de7b2fb0 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,12 @@ public ReferenceInlineSetting ReferenceInline /// True if it should be inlined otherwise false. public bool ShouldInlineReference(AsyncApiReference reference) { - return this.InlineReferences; + if (reference.IsExternal) + { + return false; + } + + return this.InlineLocalReferences; } } } diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs index a278ebcd..0118e2b4 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/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs b/src/LEGO.AsyncAPI/Writers/IAsyncApiWriter.cs index 7faff47f..e85bb834 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 { + AsyncApiWorkspace Workspace { get; set; } + /// /// Write the start object. /// diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 355d4f87..9b863969 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() @@ -321,25 +300,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "receiveLightMeasurement", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - Id = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "lightMeasured", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/lightMeasured"), }, }, }) @@ -350,14 +315,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() @@ -365,25 +323,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "turnOn", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - Id = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "turnOnOff", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/turnOnOff"), }, }, }) @@ -394,14 +338,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() @@ -409,25 +346,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "turnOff", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - Id = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "turnOnOff", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/turnOnOff"), }, }, }) @@ -438,14 +361,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() @@ -453,25 +369,11 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() OperationId = "dimLight", Traits = new List { - new AsyncApiOperationTrait() - { - Reference = new AsyncApiReference() - { - Id = "kafka", - Type = ReferenceType.OperationTrait, - }, - }, + new AsyncApiOperationTraitReference("#/components/operationTraits/kafka"), }, Message = new List { - new AsyncApiMessage() - { - Reference = new AsyncApiReference() - { - Id = "dimLight", - Type = ReferenceType.Message, - }, - }, + new AsyncApiMessageReference("#/components/messages/dimLight"), }, }, }) @@ -483,23 +385,9 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() ContentType = "application/json", Traits = new List() { - new AsyncApiMessageTrait() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.MessageTrait, - Id = "commonHeaders", - }, - }, - }, - Payload = new AsyncApiJsonSchemaPayload - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "lightMeasuredPayload", - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/lightMeasuredPayload"), }) .WithComponent("turnOnOff", new AsyncApiMessage() { @@ -508,23 +396,9 @@ 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, - Id = "commonHeaders", - }, - }, - }, - Payload = new AsyncApiJsonSchemaPayload() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "turnOnOffPayload", - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/turnOnOffPayload"), }) .WithComponent("dimLight", new AsyncApiMessage() { @@ -533,23 +407,9 @@ 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, - Id = "commonHeaders", - }, - }, - }, - Payload = new AsyncApiJsonSchemaPayload() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "dimLightPayload", - }, + new AsyncApiMessageTraitReference("#/components/messageTraits/commonHeaders"), }, + Payload = new AsyncApiJsonSchemaReference("#/components/schemas/dimLightPayload"), }) .WithComponent("lightMeasuredPayload", new AsyncApiJsonSchema() { @@ -565,14 +425,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -594,14 +447,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -620,14 +466,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() } }, { - "sentAt", new AsyncApiJsonSchema() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.Schema, - Id = "sentAt", - }, - } + "sentAt", new AsyncApiJsonSchemaReference("#/components/schemas/sentAt") }, }, }) @@ -921,14 +760,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, } @@ -1120,7 +953,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); @@ -1177,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"); } @@ -1230,12 +1063,44 @@ 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] 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() { @@ -1246,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, - Id = "bindings", - }, - }, + Bindings = new AsyncApiBindingsReference("#/components/serverBindings/bindings") }); doc.Components = new AsyncApiComponents() { @@ -1266,14 +1124,7 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() { Description = "test", }, - Bindings = new AsyncApiBindings() - { - Reference = new AsyncApiReference() - { - Type = ReferenceType.ChannelBindings, - Id = "bindings", - }, - }, + Bindings = new AsyncApiBindingsReference("#/components/channelBindings/bindings") } }, }, @@ -1305,20 +1156,20 @@ 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); + 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/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/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs index 036fae56..881b7826 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; @@ -23,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() { @@ -432,7 +457,7 @@ public void Read_WithBasicPlusSecuritySchemeDeserializes() [Test] public void Read_WithWrongReference_AddsError() { - var yaml = + var yaml = """ asyncapi: 2.3.0 info: @@ -452,7 +477,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] @@ -700,7 +725,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/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/LEGO.AsyncAPI.Tests.csproj b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj index bd35d2ae..46589db0 100644 --- a/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj +++ b/test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj @@ -10,10 +10,9 @@ - + - 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/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs index 4fbc2512..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.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/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index b3705e45..c2e7142e 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() { @@ -126,7 +126,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() actual.Should() .BePlatformAgnosticEquivalentTo(expected); message.Should().BeEquivalentTo(deserializedMessage); - } + } [Test] public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() @@ -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,9 +232,11 @@ public void AsyncApiMessage_WithAvroAsReference_Deserializes() var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _); // Assert - 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(); } @@ -326,7 +328,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 16d58b4c..3509fc1a 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -1,16 +1,168 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Tests { + using System; + using System.IO; 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.IsFragment.Should().BeTrue(); + } + + [Test] + 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": "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.FragmentId.Should().Be("/servers/0"); + reference.Reference.IsFragment.Should().BeTrue(); + reference.Url.Should().Be("wss://production.gigantic-server.com:443"); + } + + [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.IsFragment.Should().BeTrue(); + reference.Url.Should().Be("wss://production.gigantic-server.com:443"); + + } + [Test] public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() { @@ -26,12 +178,12 @@ 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; 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); @@ -55,14 +207,14 @@ 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; reference.Type.Should().Be(ReferenceType.Schema); reference.ExternalResource.Should().Be("/fragments/myFragment"); - reference.Id.Should().BeNull(); - reference.IsFragment.Should().BeTrue(); + reference.FragmentId.Should().BeNull(); + reference.IsFragment.Should().BeFalse(); reference.IsExternal.Should().BeTrue(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); actual.Should() @@ -84,12 +236,12 @@ 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.Id.Should().Be("test"); - reference.IsFragment.Should().BeFalse(); + reference.FragmentId.Should().Be("/components/schemas/test"); + reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeFalse(); var expected = deserialized.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); @@ -112,10 +264,10 @@ 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.Id.Should().Be("/fragment"); + reference.FragmentId.Should().Be("/fragment"); reference.IsFragment.Should().BeTrue(); reference.IsExternal.Should().BeTrue(); @@ -139,12 +291,12 @@ 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); - 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); @@ -181,47 +333,16 @@ 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"); 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() { @@ -247,13 +368,13 @@ 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(); 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(); } @@ -273,10 +394,10 @@ 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.Id.Should().BeNull(); + reference.FragmentId.Should().BeNull(); reference.IsExternal.Should().BeTrue(); reference.IsFragment.Should().BeFalse(); diagnostic.Errors.Should().BeEmpty(); @@ -305,18 +426,18 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceReader = new MockExternalReferenceReader(), + ExternalReferenceLoader = new MockJsonSchemaLoader(), }; var reader = new AsyncApiStringReader(settings); 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(); - payload.Properties.Count.Should().Be(3); + var payload = message.Payload.As(); + payload.Properties.Count.Should().Be(1); } [Test] - public void AvroReference_WithExternalResourcesInterface_DeserializesCorrectly() + public void AsyncApiReference_DocumentLevelReferencePointer_DeserializesCorrectly() { var yaml = """ asyncapi: 2.3.0 @@ -327,114 +448,148 @@ public void AvroReference_WithExternalResourcesInterface_DeserializesCorrectly() workspace: publish: message: - schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0' - payload: - $ref: 'path/to/user-create.avsc/#UserCreate' + 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() + { + 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.6.0 + info: + title: test + version: 1.0.0 + channels: + workspace: + publish: + message: + payload: + $ref: ./some/path/to/external/payload.json + schemaFormat: application/vnd.apache.avro + name: Test + title: Test message + summary: Test. + """; var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceReader = new MockExternalAvroReferenceReader(), + ExternalReferenceLoader = new MockStringLoader(avroPayload), }; 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"); + 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 MockExternalAvroReferenceReader : IAsyncApiExternalReferenceReader + public class MockStringLoader : IStreamLoader { - public string Load(string reference) + public MockStringLoader(string input) { - return - """ - { - "type": "record", - "name": "SomeEvent", - "namespace": "my.namspace.for.event", - "fields": [ - { - "name": "countryCode", - "type": "string", - "doc": "Country of the partner, (e.g. DE)" - }, - { - "name": "occurredOn", - "type": "string", - "doc": "Timestamp of when action occurred." - }, - { - "name": "partnerId", - "type": "string", - "doc": "Id of the partner" - }, - { - "name": "platformSource", - "type": "string", - "doc": "Platform source" - } - ], - "example": { - "occurredOn": "2023-11-03T09:56.582+00:00", - "partnerId": "1", - "platformSource": "Brecht", - "countryCode": "DE" - } - } - """; - } + this.input = input; + } + + private readonly string input; + + public Stream Load(Uri uri) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(this.input); + writer.Flush(); + stream.Position = 0; + return stream; + } + + public Task LoadAsync(Uri uri) + { + return Task.FromResult(this.Load(uri)); + } } - public class MockExternalReferenceReader : IAsyncApiExternalReferenceReader + + public class MockJsonSchemaLoader : 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" + """; + + const string Schema = + """ + type: object + properties: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + """; + + public Stream Load(Uri uri) { - if (reference == "./some/path/to/external/message.yaml") + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + if (uri.ToString() == "./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" - """; + writer.Write(Message); + } + else + { + writer.Write(Schema); } + writer.Flush(); + stream.Position = 0; + return stream; + } - 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 - """; + public Task LoadAsync(Uri uri) + { + return Task.FromResult(this.Load(uri)); } } } \ No newline at end of file diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 7e00efab..af2badf8 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -1,4 +1,4 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Tests.Models { @@ -220,12 +220,6 @@ public class AsyncApiSchema_Should : TestBase { Url = new Uri("http://example.com/externalDocs"), }, - - Reference = new AsyncApiReference - { - Type = ReferenceType.Schema, - Id = "schemaObject1", - }, }; public static AsyncApiJsonSchema AdvancedSchemaWithRequiredPropertiesObject = new AsyncApiJsonSchema @@ -370,7 +364,7 @@ public void SerializeAsJson_WithAdvancedSchemaObject_V2Works() public void Deserialize_WithAdvancedSchema_Works() { // Arrange - var json = GetTestData(); + var json = this.GetTestData(); var expected = AdvancedSchemaObject; // Act @@ -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, Id = "testC" } } }, - { "testB", new AsyncApiJsonSchema { Reference = new AsyncApiReference { Type = ReferenceType.Schema, Id = "testB" } } }, + { "testC", new AsyncApiJsonSchemaReference("#/components/schemas/testC") }, + { "testB", new AsyncApiJsonSchemaReference("#/components/schemas/testB") }, }, }, }, @@ -440,14 +434,14 @@ 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 AsyncApiJsonSchemaReference("#/components/schemas/testD") }, }, }) .WithComponent("testB", new AsyncApiJsonSchema() { Description = "test", Type = SchemaType.Boolean }) .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/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/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() 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 0b506e695061be7271815c47e63813caf4ea547d Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 31 Jan 2025 22:18:09 +0100 Subject: [PATCH 28/29] docs: Update README.md --- README.md | 155 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 9f882a1a..19f27895 100644 --- a/README.md +++ b/README.md @@ -35,39 +35,72 @@ Main classes to know: ### Writing ```csharp -var myFirstAsyncApi = new AsyncApiDocument -{ - Info = new AsyncApiInfo - { - Title = "my first asyncapi" - }, - Channels = new Dictionary - { - { - "users", new AsyncApiChannel - { - Subscribe = new AsyncApiOperation - { - OperationId = "users", - Description = "my users channel" - } - } - } - } -}; -var yaml = myFirstAsyncApi.SerializeAsYaml(); -//asyncapi: '2.5.0' + var myFirstAsyncApi = new AsyncApiDocument + { + Info = new AsyncApiInfo + { + Title = "my first asyncapi", + }, + Channels = new Dictionary + { + { + "users", new AsyncApiChannel + { + Subscribe = new AsyncApiOperation + { + OperationId = "users", + Description = "my users channel", + Message = new List + { + new AsyncApiMessageReference("#/components/messages/MyMessage"), + }, + }, + } + }, + }, + Components = new AsyncApiComponents + { + Messages = new Dictionary + { + { + "MyMessage", new AsyncApiMessage + { + Name = "Hello!", + } + }, + }, + }, + }; + +var yaml = myFirstAsyncApi.SerializeAsYaml(AsyncApi); + +//asyncapi: 2.6.0 // info: // title: my first asyncapi -// channels: -// users: -// subscribe: -// operationId: users -// description: my users channel +//channels: +// users: +// subscribe: +// operationId: users +// description: my users channel +// message: +// $ref: '#/components/messages/MyMessage' +//components: +// messages: +// MyMessage: +// name: Hello! ``` + ### Reading +There are 3 reader types +1. AsyncApiStringReader +2. AsyncApiTextReader +3. AsyncApiStreamReader + +All 3 supports both json and yaml. + +#### StreamReader ```csharp var httpClient = new HttpClient { @@ -78,47 +111,45 @@ var stream = await httpClient.GetStreamAsync("master/examples/streetlights-kafka var asyncApiDocument = new AsyncApiStreamReader().Read(stream, out var diagnostic); ``` -#### Reading External $ref +#### StringReader +```csharp +var yaml = + """ + asyncapi: 2.6.0 + info: + title: my first asyncapi + channels: + users: + subscribe: + operationId: users + description: my users channel + message: + $ref: '#/components/messages/MyMessage' + components: + messages: + MyMessage: + name: Hello! + """; + +var asyncApiDocument = new AsyncApiStringReader().Read(yaml, out var diagnostic); +``` +All readers will write warnings and errors to the diagnostics. -You can read externally referenced AsyncAPI documents by setting the `ReferenceResolution` property of the `AsyncApiReaderSettings` object to `ReferenceResolutionSetting.ResolveAllReferences` and providing an implementation for the `IAsyncApiExternalReferenceReader` interface. This interface contains a single method to which the built AsyncAPI.NET reader library will pass the location content contained in a `$ref` property (usually some form of path) and interface will return the content which is retrieved from wherever the `$ref` points to as a `string`. The AsyncAPI.NET reader will then automatically infer the `T` type of the content and recursively parse the external content into an AsyncAPI document as a child of the original document that contained the `$ref`. This means that you can have externally referenced documents that themselves contain external references. -This interface allows users to load the content of their external reference however and from whereever is required. A new instance of the implementor of `IAsyncApiExternalReferenceReader` should be registered with the `ExternalReferenceReader` property of the `AsyncApiReaderSettings` when creating the reader from which the `GetExternalResource` method will be called when resolving external references. +### Reference Resolution +Internal references are resolved by default. This includes component and non-component references e.g `#/components/messages/MyMessage` and `#/servers/0`. +External references can be resolved by setting `ReferenceResolution` to `ResolveAllReferences`. +The default implementation will resolve anything prefixed with `file://`, `http://` & `https://`, however a custom implementation can be made, by inhereting from the `IStreamLoader` interface and setting the `ExternalReferenceLoader` in the `AsyncApiReaderSettings`. +External references are always force converted to Json during resolution. This means that both yaml and json is supported - but not other serialization languages. -Below is a very simple example of implementation for `IAsyncApiExternalReferenceReader` that simply loads a file and returns it as a string found at the reference endpoint. ```csharp -using System.IO; - -public class AsyncApiExternalFileSystemReader : IAsyncApiExternalReferenceReader -{ - public string Load(string reference) - { - return File.ReadAllText(reference); - } -} +var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolution.ResolveAllReferences }; +var document = new AsyncApiStringReader(settings).Read(json, out var diagnostics); ``` -This can then be configured in the reader as follows: -```csharp -var settings = new AsyncApiReaderSettings -{ - ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - ExternalReferenceReader = new AsyncApiExternalFileSystemReader(), -}; -var reader = new AsyncApiStringReader(settings); -``` -This would function for a AsyncAPI document with following reference to load the content of `message.yaml` as a `AsyncApiMessage` object inline with the document object. -```yaml -asyncapi: 2.3.0 -info: - title: test - version: 1.0.0 -channels: - workspace: - publish: - message: - $ref: "../../../message.yaml" -``` + +Reference resolution can be disabled by setting `ReferenceResolution` to `DoNotResolveReferences`. ### Bindings To add support for reading bindings, simply add the bindings you wish to support, to the `Bindings` collection of `AsyncApiReaderSettings`. @@ -127,7 +158,7 @@ There is a nifty helper to add different types of bindings, or like in the examp ```csharp var settings = new AsyncApiReaderSettings(); settings.Bindings = BindingsCollection.All; -var asyncApiDocument = new AsyncApiStringReader(settings).Read(stream, out var diagnostic); +var asyncApiDocument = new AsyncApiStringReader(settings).Read(yaml, out var diagnostic); ``` ## Attribution From b6368fc35108a735a54858adf7b6c0a3e038876b Mon Sep 17 00:00:00 2001 From: DominikKaloc Date: Wed, 10 Sep 2025 10:08:13 +0200 Subject: [PATCH 29/29] fix: empty channels allowed (#209) --- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 2 +- .../Validation/Rules/AsyncApiDocumentRules.cs | 3 +-- test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs | 6 ++++++ test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs | 8 ++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 5d90e7a8..0359d083 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -44,7 +44,7 @@ public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable /// /// REQUIRED. The available channels and messages for the API. /// - public IDictionary Channels { get; set; } = new Dictionary(); + public IDictionary Channels { get; set; } /// /// an element to hold various schemas for the specification. diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs index e82f3c7c..2f759905 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs @@ -37,8 +37,7 @@ public static class AsyncApiDocumentRules context.Enter("channels"); try { - // MUST have at least 1 channel - if (document.Channels == null || !document.Channels.Keys.Any()) + if (document.Channels == null) { context.CreateError( nameof(DocumentRequiredFields), diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs index 2967fd8c..3a5ad497 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Tests { using System; + using System.Collections.Generic; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; @@ -42,6 +43,11 @@ public AsyncApiDocumentBuilder WithDefaultContentType(string contentType = "appl public AsyncApiDocumentBuilder WithChannel(string key, AsyncApiChannel channel) { + if (this.document.Channels == null) + { + this.document.Channels = new Dictionary(); + } + this.document.Channels.Add(key, channel); return this; } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 9b863969..94c09dbe 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1154,6 +1154,10 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() }, }, }; + if (doc.Channels == null) + { + doc.Channels = new Dictionary(); + } doc.Channels.Add( "testChannel", new AsyncApiChannelReference("#/components/channels/otherchannel")); @@ -1212,6 +1216,10 @@ public void Serializev2_WithBindings_Serializes() Protocol = "pulsar+ssl", Url = "example.com", }); + if (doc.Channels == null) + { + doc.Channels = new Dictionary(); + } doc.Channels.Add( "testChannel", new AsyncApiChannel