From 3d270e77f72ae35153a0d56279fa5ccc4c9e42c4 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 17 May 2024 15:13:59 +0200 Subject: [PATCH 01/20] First try of implementing avro still missing a few bits and bobs --- .../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 | 5 +- .../ParseNodes/PropertyNode.cs | 2 +- .../V2/AsyncApiComponentsDeserializer.cs | 2 +- .../V2/AsyncApiMessageDeserializer.cs | 54 +++++++- .../V2/AsyncApiMessageTraitDeserializer.cs | 2 +- .../V2/AsyncApiParameterDeserializer.cs | 2 +- .../V2/AsyncApiSchemaDeserializer.cs | 2 +- .../V2/AsyncApiV2VersionService.cs | 3 +- .../Models/AsyncApiAvroSchemaPayload.cs | 16 +++ src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs | 2 +- .../Models/AsyncApiSchemaPayload.cs | 128 ++++++++++++++++++ .../Models/Interfaces/IAsyncApiPayload.cs | 8 ++ .../Services/AsyncApiReferenceResolver.cs | 6 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 6 +- .../AsyncApiDocumentV2Tests.cs | 6 +- .../Models/AsyncApiMessage_Should.cs | 13 +- .../Models/AsyncApiReference_Should.cs | 25 ++-- .../Models/AsyncApiSchema_Should.cs | 2 +- 24 files changed, 257 insertions(+), 43 deletions(-) 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/Interfaces/IAsyncApiPayload.cs 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/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/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..8ee73803 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -91,7 +91,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 AsyncApiSchemaPayload) + { + this.ResolveObject(message.Payload as AsyncApiSchemaPayload, 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/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/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..4e0b49e0 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -5,10 +5,19 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.ParseNodes; + using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; + + internal static partial class AsyncApiV2Deserializer + { + + } + /// /// Class containing logic to deserialize AsyncApi document into /// runtime AsyncApi object model. @@ -21,10 +30,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 +73,34 @@ internal static partial class AsyncApiV2Deserializer }, }; - static readonly IEnumerable SupportedSchemaFormats = new List + public static IAsyncApiMessagePayload LoadPayload(ParseNode n) + { + // #ToFix figure out a way to get the format in a proper way. + return LoadPayload(n, null); + } + + 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 AsyncApiSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n)); + case var _ when SupportedAvroSchemaFormats.Where(s => format.StartsWith(s)).Any(): + return new AsyncApiAvroSchemaPayload(); + 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 +109,18 @@ internal static partial class AsyncApiV2Deserializer "application/schema+yaml;version=draft-07", }; + static readonly IEnumerable SupportedAvroSchemaFormats = new List + { + "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..bed3d692 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() { diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 10edd3ba..cc9184f3 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -35,7 +35,8 @@ 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(AsyncApiSchemaPayload)] = AsyncApiV2Deserializer.LoadPayload, // #ToFix how do we get the schemaFormat?! [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, [typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme, [typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer, diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs new file mode 100644 index 00000000..e2e615b7 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -0,0 +1,16 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload + { + public void SerializeV2(IAsyncApiWriter writer) + { + throw new NotImplementedException(); + } + } +} 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/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs new file mode 100644 index 00000000..edda5f8d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -0,0 +1,128 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AsyncApiSchemaPayload : IAsyncApiMessagePayload, IAsyncApiReferenceable + { + private readonly AsyncApiSchema schema; + + public AsyncApiSchemaPayload() + { + this.schema = new AsyncApiSchema(); + } + + public AsyncApiSchemaPayload(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(AsyncApiSchemaPayload payload) => payload.schema; + + public static implicit operator AsyncApiSchemaPayload(AsyncApiSchema schema) => new AsyncApiSchemaPayload(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/Interfaces/IAsyncApiPayload.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs new file mode 100644 index 00000000..1da08177 --- /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 + { + } +} diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 516c8aac..94cad83b 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -86,7 +86,11 @@ 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 AsyncApiSchemaPayload) + { + this.ResolveObject(message.Payload as AsyncApiSchemaPayload, 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..78f4e0b3 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -501,7 +501,11 @@ 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 AsyncApiSchemaPayload 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/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 63e818b4..a97f96a7 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -493,7 +493,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiSchemaPayload { Reference = new AsyncApiReference() { @@ -518,7 +518,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiSchemaPayload() { Reference = new AsyncApiReference() { @@ -543,7 +543,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiSchemaPayload() { Reference = new AsyncApiReference() { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index a37f1503..a372069c 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 AsyncApiSchemaPayload() { 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 @@ -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 AsyncApiSchemaPayload() { Properties = new Dictionary() { @@ -257,7 +256,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() }), }, }, - Payload = new AsyncApiSchema() + Payload = new AsyncApiSchemaPayload() { Properties = new Dictionary { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 9e139cc4..967d3fdd 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -26,9 +26,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 +55,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 +84,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 +112,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 +139,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 +273,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 +311,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..19852381 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -421,7 +421,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl { new AsyncApiMessage { - Payload = new AsyncApiSchema + Payload = new AsyncApiSchemaPayload { Type = SchemaType.Object, Required = new HashSet { "testB" }, From 56ff634a645e6dac458cd50ea5d979c735d36071 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 23 May 2024 12:15:40 +0200 Subject: [PATCH 02/20] added avro models --- .../AsyncApiExternalReferenceResolver.cs | 4 +- .../V2/AsyncApiMessageDeserializer.cs | 10 ++- .../V2/AsyncApiV2VersionService.cs | 3 +- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 1 + .../Models/AsyncApiSchemaPayload.cs | 10 +-- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 26 +++++++ src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 35 ++++++++++ src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 67 +++++++++++++++++++ .../Models/Avro/AvroFieldType.cs | 15 +++++ src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 28 ++++++++ src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 26 +++++++ .../Models/Avro/AvroPrimitive.cs | 25 +++++++ src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 35 ++++++++++ src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 63 +++++++++++++++++ .../Models/Avro/AvroSchemaType.cs | 56 ++++++++++++++++ src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 28 ++++++++ .../Services/AsyncApiReferenceResolver.cs | 7 +- src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 3 +- .../AsyncApiDocumentV2Tests.cs | 6 +- .../Models/AsyncApiMessage_Should.cs | 8 +-- .../Models/AsyncApiReference_Should.cs | 14 ++-- .../Models/AsyncApiSchema_Should.cs | 2 +- 22 files changed, 442 insertions(+), 30 deletions(-) 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/AvroFieldType.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/AvroRecord.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index 8ee73803..00e7f077 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs @@ -91,9 +91,9 @@ public override void Visit(AsyncApiOperation operation) public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - if (message.Payload is AsyncApiSchemaPayload) + if (message.Payload is AsyncApiJsonSchemaPayload) { - this.ResolveObject(message.Payload as AsyncApiSchemaPayload, r => message.Payload = r); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); } this.ResolveList(message.Traits); this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 4e0b49e0..46439764 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -73,12 +73,16 @@ internal static partial class AsyncApiV2Deserializer }, }; - public static IAsyncApiMessagePayload LoadPayload(ParseNode n) + public static IAsyncApiMessagePayload LoadJsonSchemaPayload(ParseNode n) { - // #ToFix figure out a way to get the format in a proper way. 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) @@ -91,7 +95,7 @@ private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) case null: case "": case var _ when SupportedJsonSchemaFormats.Where(s => format.StartsWith(s)).Any(): - return new AsyncApiSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n)); + return new AsyncApiJsonSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n)); case var _ when SupportedAvroSchemaFormats.Where(s => format.StartsWith(s)).Any(): return new AsyncApiAvroSchemaPayload(); default: diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index cc9184f3..0f9d3b06 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -36,7 +36,8 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation, [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema, - [typeof(AsyncApiSchemaPayload)] = AsyncApiV2Deserializer.LoadPayload, // #ToFix how do we get the schemaFormat?! + [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, // #ToFix how do we get the schemaFormat?! + [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, [typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme, [typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer, diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 3bfe1c9c..5559d0f5 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -15,6 +15,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs index edda5f8d..69489235 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -7,16 +7,16 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public class AsyncApiSchemaPayload : IAsyncApiMessagePayload, IAsyncApiReferenceable + public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload, IAsyncApiReferenceable { private readonly AsyncApiSchema schema; - public AsyncApiSchemaPayload() + public AsyncApiJsonSchemaPayload() { this.schema = new AsyncApiSchema(); } - public AsyncApiSchemaPayload(AsyncApiSchema schema) + public AsyncApiJsonSchemaPayload(AsyncApiSchema schema) { this.schema = schema; } @@ -111,9 +111,9 @@ public AsyncApiSchemaPayload(AsyncApiSchema schema) public AsyncApiSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; } - public static implicit operator AsyncApiSchema(AsyncApiSchemaPayload payload) => payload.schema; + public static implicit operator AsyncApiSchema(AsyncApiJsonSchemaPayload payload) => payload.schema; - public static implicit operator AsyncApiSchemaPayload(AsyncApiSchema schema) => new AsyncApiSchemaPayload(schema); + public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiSchema schema) => new AsyncApiJsonSchemaPayload(schema); public void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs new file mode 100644 index 00000000..cfed5ca5 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -0,0 +1,26 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroArray : AvroFieldType + { + public string Type { get; set; } = "array"; + + public AvroFieldType Items { get; set; } + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WritePropertyName("items"); + this.Items.SerializeV2(writer); + 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..e3302355 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -0,0 +1,35 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroEnum : AvroFieldType + { + public string Type { get; set; } = "enum"; + + public string Name { get; set; } + + public IList Symbols { get; set; } = new List(); + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WritePropertyName("symbols"); + writer.WriteStartArray(); + foreach (var symbol in this.Symbols) + { + writer.WriteValue(symbol); + } + + writer.WriteEndArray(); + 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..84778a31 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -0,0 +1,67 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// 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 AvroFieldType 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 string Order { get; set; } + + public void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + + // name + writer.WriteOptionalProperty("name", this.Name); + + // type + if (this.Type != null) + { + writer.WritePropertyName("type"); + this.Type.SerializeV2(writer); + } + + // doc + writer.WriteOptionalProperty("doc", this.Doc); + + // default + writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s)); + + // order + writer.WriteOptionalProperty("order", this.Order); + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs new file mode 100644 index 00000000..b53e4e50 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs @@ -0,0 +1,15 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public abstract class AvroFieldType : IAsyncApiSerializable + { + public abstract void SerializeV2(IAsyncApiWriter writer); + } +} \ 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..5f19f7c1 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -0,0 +1,28 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroFixed : AvroFieldType + { + public string Type { get; set; } = "fixed"; + + public string Name { get; set; } + + public int Size { get; set; } + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteRequiredProperty("size", this.Size); + 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..59e7289d --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -0,0 +1,26 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroMap : AvroFieldType + { + public string Type { get; set; } = "map"; + + public AvroFieldType Values { get; set; } + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WritePropertyName("values"); + this.Values.SerializeV2(writer); + 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..0be1dfe1 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -0,0 +1,25 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroPrimitive : AvroFieldType + { + public string Type { get; set; } + + public AvroPrimitive(string type) + { + this.Type = type; + } + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteValue(this.Type); + } + } +} \ 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..858e9f27 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -0,0 +1,35 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroRecord : AvroFieldType + { + public string Type { get; set; } = "record"; + + public string Name { get; set; } + + public IList Fields { get; set; } = new List(); + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + writer.WriteOptionalProperty("type", this.Type); + writer.WriteRequiredProperty("name", this.Name); + writer.WritePropertyName("fields"); + writer.WriteStartArray(); + foreach (var field in this.Fields) + { + field.SerializeV2(writer); + } + + writer.WriteEndArray(); + 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..73eab2c7 --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -0,0 +1,63 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + /// + /// Represents an Avro schema model (compatible with Avro 1.9.0). + /// + public class AvroSchema : IAsyncApiSerializable + { + /// + /// The type of the schema. See Avro Schema Types. + /// + public AvroSchemaType Type { get; set; } + + /// + /// The name of the schema. Required for named types (e.g., record, enum, fixed). 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; } + + public void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + + // name + writer.WriteOptionalProperty("name", this.Name); + + // namespace + writer.WriteOptionalProperty("namespace", this.Namespace); + + // type + var types = EnumExtensions.GetFlags(this.Type); + if (types.Count() == 1) + { + writer.WriteOptionalProperty(AsyncApiConstants.Type, types.First().GetDisplayName()); + } + else + { + writer.WriteOptionalCollection(AsyncApiConstants.Type, types.Select(t => t.GetDisplayName()), (w, s) => w.WriteValue(s)); + } + + // doc + writer.WriteOptionalProperty("doc", this.Doc); + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs new file mode 100644 index 00000000..0ffc8bad --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs @@ -0,0 +1,56 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using LEGO.AsyncAPI.Attributes; + + /// + /// Enumeration of Avro schema types. See Avro Schemas. + /// + [Flags] + public enum AvroSchemaType + { + [Display("null")] + Null = 1, + + [Display("boolean")] + Boolean = 2, + + [Display("int")] + Int = 4, + + [Display("long")] + Long = 8, + + [Display("float")] + Float = 16, + + [Display("double")] + Double = 32, + + [Display("bytes")] + Bytes = 64, + + [Display("string")] + String = 128, + + [Display("record")] + Record = 256, + + [Display("enum")] + Enum = 512, + + [Display("array")] + Array = 1024, + + [Display("map")] + Map = 2048, + + [Display("fixed")] + Fixed = 4096, + + [Display("logical")] + Logical = 8192, + } +} \ 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..8878c5ff --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -0,0 +1,28 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class AvroUnion : AvroFieldType + { + public IList Types { get; set; } = new List(); + + public override void SerializeV2(IAsyncApiWriter writer) + { + writer.WriteStartArray(); + foreach (var type in this.Types) + { + writer.WriteStartObject(); + type.SerializeV2(writer); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 94cad83b..32cd435d 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -86,10 +86,11 @@ public override void Visit(AsyncApiOperation operation) public override void Visit(AsyncApiMessage message) { this.ResolveObject(message.Headers, r => message.Headers = r); - // #ToFix Resolve references correctly - if (message.Payload is AsyncApiSchemaPayload) + + // #ToFix Resolve references correctly + if (message.Payload is AsyncApiJsonSchemaPayload) { - this.ResolveObject(message.Payload as AsyncApiSchemaPayload, r => message.Payload = r); + this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r); } this.ResolveList(message.Traits); this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r); diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index 78f4e0b3..a2108647 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -501,10 +501,11 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) if (message != null) { this.Walk(AsyncApiConstants.Headers, () => this.Walk(message.Headers)); - if (message.Payload is AsyncApiSchemaPayload 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)); diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index a97f96a7..458e3f14 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -493,7 +493,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchemaPayload + Payload = new AsyncApiJsonSchemaPayload { Reference = new AsyncApiReference() { @@ -518,7 +518,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchemaPayload() + Payload = new AsyncApiJsonSchemaPayload() { Reference = new AsyncApiReference() { @@ -543,7 +543,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes() }, }, }, - Payload = new AsyncApiSchemaPayload() + Payload = new AsyncApiJsonSchemaPayload() { Reference = new AsyncApiReference() { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index a372069c..aed6fd77 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 AsyncApiSchemaPayload() + message.Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary() { @@ -145,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 AsyncApiSchemaPayload() + message.Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary() { @@ -256,7 +256,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes() }), }, }, - Payload = new AsyncApiSchemaPayload() + Payload = new AsyncApiJsonSchemaPayload() { Properties = new Dictionary { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs index 967d3fdd..64e34c57 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs @@ -26,7 +26,7 @@ public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); payload.UnresolvedReference.Should().BeTrue(); var reference = payload.Reference; @@ -55,7 +55,7 @@ public void AsyncApiReference_WithFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); payload.UnresolvedReference.Should().BeTrue(); var reference = payload.Reference; @@ -84,7 +84,7 @@ 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); @@ -112,7 +112,7 @@ public void AsyncApiReference_WithExternalFragmentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("./myjsonfile.json"); reference.Id.Should().Be("/fragment"); @@ -139,7 +139,7 @@ public void AsyncApiReference_WithExternalComponentReference_AllowReference() // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("./someotherdocument.json"); reference.Type.Should().Be(ReferenceType.Schema); @@ -273,7 +273,7 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv // Assert diagnostic.Errors.Should().BeEmpty(); - var payload = deserialized.Payload.As(); + var payload = deserialized.Payload.As(); var reference = payload.Reference; reference.ExternalResource.Should().Be("http://example.com/json.json"); reference.Id.Should().BeNull(); @@ -311,7 +311,7 @@ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrect var doc = reader.Read(yaml, out var diagnostic); var message = doc.Channels["workspace"].Publish.Message.First(); message.Name.Should().Be("Test"); - var payload = message.Payload.As(); + var payload = message.Payload.As(); payload.Properties.Count.Should().Be(3); } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 19852381..e05951e7 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -421,7 +421,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl { new AsyncApiMessage { - Payload = new AsyncApiSchemaPayload + Payload = new AsyncApiJsonSchemaPayload { Type = SchemaType.Object, Required = new HashSet { "testB" }, From c96dab3ce9e10ae132b6e97d98f86e98604d42a1 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 23 May 2024 14:01:11 +0200 Subject: [PATCH 03/20] finished writing --- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 1 - src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs | 1 - .../Models/AsyncApiSchemaPayload.cs | 1 - src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 9 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 14 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 9 +- .../Models/Avro/AvroFieldType.cs | 8 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 6 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 9 +- .../Models/Avro/AvroPrimitive.cs | 11 +- .../Models/Avro/AvroPrimitiveType.cs | 33 ++++ src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 12 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 23 ++- .../Models/Avro/AvroSchemaType.cs | 40 +--- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 6 - .../Writers/AsyncApiWriterExtensions.cs | 16 ++ .../SpecialCharacterStringExtensions.cs | 5 - .../Models/AvroSchema_Should.cs | 174 ++++++++++++++++++ 18 files changed, 255 insertions(+), 123 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs create mode 100644 test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 5559d0f5..3bfe1c9c 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -15,7 +15,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive 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/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs index 69489235..a0344702 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; - using System.Runtime.CompilerServices; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index cfed5ca5..64fb1657 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -2,15 +2,11 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroArray : AvroFieldType { - public string Type { get; set; } = "array"; + public string Type { get; } = "array"; public AvroFieldType Items { get; set; } @@ -18,8 +14,7 @@ public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); - writer.WritePropertyName("items"); - this.Items.SerializeV2(writer); + writer.WriteRequiredObject("items", this.Items, (w, f) => f.SerializeV2(w)); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index e3302355..f302c923 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -2,15 +2,12 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroEnum : AvroFieldType { - public string Type { get; set; } = "enum"; + public string Type { get; } = "enum"; public string Name { get; set; } @@ -21,14 +18,7 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WritePropertyName("symbols"); - writer.WriteStartArray(); - foreach (var symbol in this.Symbols) - { - writer.WriteValue(symbol); - } - - writer.WriteEndArray(); + writer.WriteRequiredCollection("symbols", this.Symbols, (w, s) => w.WriteValue(s)); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 84778a31..1ae04bb1 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -2,9 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -46,11 +43,7 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteOptionalProperty("name", this.Name); // type - if (this.Type != null) - { - writer.WritePropertyName("type"); - this.Type.SerializeV2(writer); - } + writer.WriteOptionalObject("type", this.Type, (w, s) => s.SerializeV2(w)); // doc writer.WriteOptionalProperty("doc", this.Doc); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs index b53e4e50..8a53026a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs @@ -2,14 +2,16 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public abstract class AvroFieldType : IAsyncApiSerializable { + public static implicit operator AvroFieldType(AvroPrimitiveType type) + { + return new AvroPrimitive(type); + } + public abstract void SerializeV2(IAsyncApiWriter writer); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 5f19f7c1..f598c2ee 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -2,15 +2,11 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroFixed : AvroFieldType { - public string Type { get; set; } = "fixed"; + public string Type { get; } = "fixed"; public string Name { get; set; } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 59e7289d..6c5398f7 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -2,15 +2,11 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroMap : AvroFieldType { - public string Type { get; set; } = "map"; + public string Type { get; } = "map"; public AvroFieldType Values { get; set; } @@ -18,8 +14,7 @@ public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); - writer.WritePropertyName("values"); - this.Values.SerializeV2(writer); + writer.WriteRequiredObject("values", this.Values, (w, f) => f.SerializeV2(w)); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 0be1dfe1..b7db6d6c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -2,24 +2,21 @@ namespace LEGO.AsyncAPI.Models { - using System; - using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; + using System.Runtime.CompilerServices; public class AvroPrimitive : AvroFieldType { - public string Type { get; set; } + public AvroPrimitiveType Type { get; set; } - public AvroPrimitive(string type) + public AvroPrimitive(AvroPrimitiveType type) { this.Type = type; } public override void SerializeV2(IAsyncApiWriter writer) { - writer.WriteValue(this.Type); + writer.WriteValue(this.Type.GetDisplayName()); } } } \ 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 index 858e9f27..dab81d5c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -2,10 +2,7 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroRecord : AvroFieldType @@ -21,14 +18,7 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WritePropertyName("fields"); - writer.WriteStartArray(); - foreach (var field in this.Fields) - { - field.SerializeV2(writer); - } - - writer.WriteEndArray(); + writer.WriteRequiredCollection("fields", this.Fields, (w, s) => s.SerializeV2(w)); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index 73eab2c7..b837ac19 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; using System.Linq; using LEGO.AsyncAPI.Models.Interfaces; @@ -33,30 +32,30 @@ public class AvroSchema : IAsyncApiSerializable /// public string? Doc { get; set; } + /// + /// The list of fields in the schema. + /// + public IList Fields { get; set; } = new List(); + public void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); + // type + writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); + // name writer.WriteOptionalProperty("name", this.Name); // namespace writer.WriteOptionalProperty("namespace", this.Namespace); - // type - var types = EnumExtensions.GetFlags(this.Type); - if (types.Count() == 1) - { - writer.WriteOptionalProperty(AsyncApiConstants.Type, types.First().GetDisplayName()); - } - else - { - writer.WriteOptionalCollection(AsyncApiConstants.Type, types.Select(t => t.GetDisplayName()), (w, s) => w.WriteValue(s)); - } - // doc writer.WriteOptionalProperty("doc", this.Doc); + // fields + writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs index 0ffc8bad..1a5b74db 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs @@ -8,49 +8,15 @@ namespace LEGO.AsyncAPI.Models /// /// Enumeration of Avro schema types. See Avro Schemas. /// - [Flags] public enum AvroSchemaType { - [Display("null")] - Null = 1, - - [Display("boolean")] - Boolean = 2, - - [Display("int")] - Int = 4, - - [Display("long")] - Long = 8, - - [Display("float")] - Float = 16, - - [Display("double")] - Double = 32, - - [Display("bytes")] - Bytes = 64, - - [Display("string")] - String = 128, - [Display("record")] - Record = 256, + Record, [Display("enum")] - Enum = 512, - - [Display("array")] - Array = 1024, - - [Display("map")] - Map = 2048, + Enum, [Display("fixed")] - Fixed = 4096, - - [Display("logical")] - Logical = 8192, + Fixed } } \ 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 8878c5ff..57111d98 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -2,10 +2,7 @@ namespace LEGO.AsyncAPI.Models { - using System; using System.Collections.Generic; - using System.Linq; - using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AvroUnion : AvroFieldType @@ -17,11 +14,8 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartArray(); foreach (var type in this.Types) { - writer.WriteStartObject(); type.SerializeV2(writer); - writer.WriteEndObject(); } - writer.WriteEndArray(); } } 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/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/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs new file mode 100644 index 00000000..dad759cb --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -0,0 +1,174 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Tests.Models +{ + using System.Collections.Generic; + using FluentAssertions; + using LEGO.AsyncAPI.Models; + using NUnit.Framework; + + public class AvroSchema_Should + { + [Test] + public void SerializeV2_SerializesCorrectly() + { + 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 AvroSchema + { + Type = AvroSchemaType.Record, + 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 = "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.", + }, + }, + }; + + var actual = schema.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + + // Assert + actual.Should() + .BePlatformAgnosticEquivalentTo(expected); + } + } +} From a82d36b43685dcb565a04d908ddcc9e2023c9979 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 08:54:49 +0200 Subject: [PATCH 04/20] minor tests --- .../Models/AsyncApiChannel_Should.cs | 2 +- .../Models/AsyncApiSchema_Should.cs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) 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/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index e05951e7..43d15629 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -510,6 +510,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. From 03326afdff62ea7649b3bd6651d9eca9a969b871 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 08:55:19 +0200 Subject: [PATCH 05/20] initial deserializer --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs new file mode 100644 index 00000000..30752870 --- /dev/null +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -0,0 +1,114 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Readers +{ + using System.Collections.Generic; + using System.Globalization; + using LEGO.AsyncAPI.Extensions; + 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 schemaFixedFields = new() + { + { "type", (a, n) => a.Type = n.GetScalarValue().GetEnumFromDisplayName() }, + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + { "fields", (a, n) => a.Fields = n.CreateList(LoadField) }, + }; + + public static AvroSchema LoadSchema(ParseNode node) + { + var mapNode = node.CheckMapNode("schema"); + var schema = new AvroSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, schemaFixedFields, null); + } + + return schema; + } + + private static AvroField LoadField(ParseNode node) + { + var mapNode = node.CheckMapNode("field"); + var field = new AvroField(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(field, fieldFixedFields, null); + } + + return field; + } + + private static readonly FixedFieldMap fieldFixedFields = new() + { + { "name", (a, n) => a.Name = n.GetScalarValue() }, + { "type", (a, n) => a.Type = LoadFieldType(n) }, + { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + { "default", (a, n) => a.Default = n.CreateAny() }, + { "order", (a, n) => a.Order = n.GetScalarValue() }, + }; + + private static AvroFieldType LoadFieldType(ParseNode node) + { + if (node is ValueNode valueNode) + { + return new AvroPrimitive(valueNode.GetScalarValue().GetEnumFromDisplayName()); + } + + if (node is MapNode mapNode) + { + //var typeNode = mapNode.GetValue("type"); + //var type = typeNode?.GetScalarValue(); + + //switch (type) + //{ + // case "record": + // return new AvroRecord + // { + // Name = mapNode.GetValue("name")?.GetScalarValue(), + // Fields = mapNode.GetValue("fields").CreateList(LoadField) + // }; + // case "enum": + // return new AvroEnum + // { + // Name = mapNode.GetValue("name")?.GetScalarValue(), + // Symbols = mapNode.GetValue("symbols").CreateSimpleList(n => n.GetScalarValue()) + // }; + // case "fixed": + // return new AvroFixed + // { + // Name = mapNode.GetValue("name")?.GetScalarValue(), + // Size = int.Parse(mapNode.GetValue("size").GetScalarValue()) + // }; + // case "array": + // return new AvroArray + // { + // Items = LoadFieldType(mapNode.GetValue("items")) + // }; + // case "map": + // return new AvroMap + // { + // Values = LoadFieldType(mapNode.GetValue("values")) + // }; + // case "union": + // return new AvroUnion + // { + // Types = mapNode.GetValue("types").CreateList(LoadFieldType) + // }; + // default: + // throw new InvalidOperationException($"Unsupported type: {type}"); + //} + } + + throw new AsyncApiReaderException("Invalid node type"); + } + } +} \ No newline at end of file From 484fc2752504ab117b6511a3e781be7d8317db95 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 08:55:37 +0200 Subject: [PATCH 06/20] remove unneeded FieldMaps --- .../V2/AsyncApiSchemaDeserializer.cs | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs index bed3d692..b0c10de7 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs @@ -215,28 +215,6 @@ public class AsyncApiSchemaDeserializer { 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; } } From 8634d7a47ce01e30a05eab331828a93551f51b62 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 11:45:06 +0200 Subject: [PATCH 07/20] I AM AN AVRO GOD --- .../ParseNodes/MapNode.cs | 8 + .../V2/AsyncApiAvroSchemaDeserializer.cs | 135 ++++++++------ .../V2/AsyncApiV2VersionService.cs | 1 + src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- .../Models/AvroSchema_Should.cs | 167 ++++++++++++++++++ 5 files changed, 260 insertions(+), 53 deletions(-) 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/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 30752870..9e7b828b 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -2,9 +2,7 @@ namespace LEGO.AsyncAPI.Readers { - using System.Collections.Generic; - using System.Globalization; - using LEGO.AsyncAPI.Extensions; + using System; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.ParseNodes; @@ -26,10 +24,7 @@ public static AvroSchema LoadSchema(ParseNode node) var mapNode = node.CheckMapNode("schema"); var schema = new AvroSchema(); - foreach (var propertyNode in mapNode) - { - propertyNode.ParseField(schema, schemaFixedFields, null); - } + mapNode.ParseFields(ref schema, schemaFixedFields, null); return schema; } @@ -39,10 +34,7 @@ private static AvroField LoadField(ParseNode node) var mapNode = node.CheckMapNode("field"); var field = new AvroField(); - foreach (var propertyNode in mapNode) - { - propertyNode.ParseField(field, fieldFixedFields, null); - } + mapNode.ParseFields(ref field, fieldFixedFields, null); return field; } @@ -56,6 +48,44 @@ private static AvroField LoadField(ParseNode node) { "order", (a, n) => a.Order = n.GetScalarValue() }, }; + private static readonly FixedFieldMap recordFixedFields = new() + { + { "type", (a, n) => { } }, + { "name", (a, n) => a.Name = n.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() }, + { "symbols", (a, n) => a.Symbols = n.CreateSimpleList(n2 => n2.GetScalarValue()) }, + }; + + private static readonly FixedFieldMap fixedFixedFields = new() + { + { "type", (a, n) => { } }, + { "name", (a, n) => a.Name = n.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 = LoadFieldType(n) }, + }; + + private static readonly FixedFieldMap mapFixedFields = new() + { + { "type", (a, n) => { } }, + { "values", (a, n) => a.Values = LoadFieldType(n) }, + }; + + private static readonly FixedFieldMap unionFixedFields = new() + { + { "types", (a, n) => a.Types = n.CreateList(LoadFieldType) }, + }; + private static AvroFieldType LoadFieldType(ParseNode node) { if (node is ValueNode valueNode) @@ -63,49 +93,50 @@ private static AvroFieldType LoadFieldType(ParseNode node) return new AvroPrimitive(valueNode.GetScalarValue().GetEnumFromDisplayName()); } + if (node is ListNode) + { + var union = new AvroUnion(); + foreach (var item in node as ListNode) + { + union.Types.Add(LoadFieldType(item)); + } + + return union; + } + if (node is MapNode mapNode) { - //var typeNode = mapNode.GetValue("type"); - //var type = typeNode?.GetScalarValue(); - - //switch (type) - //{ - // case "record": - // return new AvroRecord - // { - // Name = mapNode.GetValue("name")?.GetScalarValue(), - // Fields = mapNode.GetValue("fields").CreateList(LoadField) - // }; - // case "enum": - // return new AvroEnum - // { - // Name = mapNode.GetValue("name")?.GetScalarValue(), - // Symbols = mapNode.GetValue("symbols").CreateSimpleList(n => n.GetScalarValue()) - // }; - // case "fixed": - // return new AvroFixed - // { - // Name = mapNode.GetValue("name")?.GetScalarValue(), - // Size = int.Parse(mapNode.GetValue("size").GetScalarValue()) - // }; - // case "array": - // return new AvroArray - // { - // Items = LoadFieldType(mapNode.GetValue("items")) - // }; - // case "map": - // return new AvroMap - // { - // Values = LoadFieldType(mapNode.GetValue("values")) - // }; - // case "union": - // return new AvroUnion - // { - // Types = mapNode.GetValue("types").CreateList(LoadFieldType) - // }; - // default: - // throw new InvalidOperationException($"Unsupported type: {type}"); - //} + var type = mapNode["type"].Value?.GetScalarValue(); + + switch (type) + { + case "record": + var record = new AvroRecord(); + mapNode.ParseFields(ref record, recordFixedFields, null); + return record; + case "enum": + var @enum = new AvroEnum(); + mapNode.ParseFields(ref @enum, enumFixedFields, null); + return @enum; + case "fixed": + var @fixed = new AvroFixed(); + mapNode.ParseFields(ref @fixed, fixedFixedFields, null); + return @fixed; + case "array": + var array = new AvroArray(); + mapNode.ParseFields(ref array, arrayFixedFields, null); + return array; + case "map": + var map = new AvroMap(); + mapNode.ParseFields(ref map, mapFixedFields, null); + return map; + case "union": + var union = new AvroUnion(); + mapNode.ParseFields(ref union, unionFixedFields, null); + return union; + default: + throw new InvalidOperationException($"Unsupported type: {type}"); + } } throw new AsyncApiReaderException("Invalid node type"); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 0f9d3b06..033eeedd 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -36,6 +36,7 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation, [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema, + [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, // #ToFix how do we get the schemaFormat?! [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index dab81d5c..bf0d87dc 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -7,7 +7,7 @@ namespace LEGO.AsyncAPI.Models public class AvroRecord : AvroFieldType { - public string Type { get; set; } = "record"; + public string Type { get; } = "record"; public string Name { get; set; } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index dad759cb..4efc0e38 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -5,6 +5,7 @@ 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 @@ -12,6 +13,7 @@ public class AvroSchema_Should [Test] public void SerializeV2_SerializesCorrectly() { + // Arrange var expected = """ type: record name: User @@ -164,11 +166,176 @@ public void SerializeV2_SerializesCorrectly() }, }; + // 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 AvroSchema + { + Type = AvroSchemaType.Record, + 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 = "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); + } } } From bf07d1a8f03165787133bc35b05561fd67f34009 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 12:13:44 +0200 Subject: [PATCH 08/20] hookup payload deserializer --- .../AMQP/AMQPOperationBinding.cs | 1 - src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs | 2 +- .../MQTT/MQTTOperationBinding.cs | 3 -- .../Sqs/RedrivePolicy.cs | 2 +- .../AsyncApiExternalReferenceResolver.cs | 1 - .../AsyncApiJsonDocumentReader.cs | 2 - .../AsyncApiTextReader.cs | 1 - .../ParseNodes/ValueNode.cs | 4 +- .../V2/AsyncApiMessageDeserializer.cs | 9 ++-- .../V2/AsyncApiV2VersionService.cs | 2 +- .../Models/AsyncApiAvroSchemaPayload.cs | 41 ++++++++++++++++++- .../Models/Avro/AvroPrimitive.cs | 1 - src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 1 - .../Models/Avro/AvroSchemaType.cs | 1 - .../Rules/AsyncApiOAuthFlowRules.cs | 2 +- .../Writers/AsyncApiWriterException.cs | 2 +- .../AsyncApiDocumentBuilder.cs | 2 +- .../AsyncApiDocumentV2Tests.cs | 1 - .../AsyncApiReaderTests.cs | 1 - .../Bindings/StringOrStringList_Should.cs | 1 - .../FluentAssertionExtensions.cs | 1 - .../Models/AsyncApiAnyTests.cs | 5 +-- .../Models/AsyncApiOperation_Should.cs | 1 - .../Models/AsyncApiReference_Should.cs | 3 +- .../Models/AsyncApiSchema_Should.cs | 1 - .../Serialization/AsyncApiYamlWriterTests.cs | 3 +- 26 files changed, 54 insertions(+), 40 deletions(-) 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/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/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/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.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs index 00e7f077..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; 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/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/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 46439764..6f0ef76c 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -2,16 +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; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - internal static partial class AsyncApiV2Deserializer { @@ -97,7 +94,7 @@ private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) 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(); + 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)}"); diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 033eeedd..4acc3395 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -37,7 +37,7 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic) [typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter, [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema, [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema, - [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, // #ToFix how do we get the schemaFormat?! + [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload, [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload, [typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement, [typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme, diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index e2e615b7..b4850d7d 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -2,15 +2,52 @@ namespace LEGO.AsyncAPI.Models { - using System; + using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload { + private AvroSchema schema; + + public AsyncApiAvroSchemaPayload() + { + this.schema = new AvroSchema(); + } + + public AsyncApiAvroSchemaPayload(AvroSchema schema) + { + this.schema = schema; + } + + /// + /// The type of the schema. See Avro Schema Types. + /// + public AvroSchemaType Type { get => this.schema.Type; set => this.schema.Type = value; } + + /// + /// The name of the schema. Required for named types (e.g., record, enum, fixed). See Avro Names. + /// + public string Name { get => this.schema.Name; set => this.schema.Name = value; } + + /// + /// The namespace of the schema. Useful for named types to avoid name conflicts. + /// + public string? Namespace { get => this.schema.Namespace; set => this.schema.Namespace = value; } + + /// + /// Documentation for the schema. + /// + public string? Doc { get => this.schema.Doc; set => this.schema.Doc = value; } + + /// + /// The list of fields in the schema. + /// + public IList Fields { get => this.schema.Fields; set => this.schema.Fields = value; } + public void SerializeV2(IAsyncApiWriter writer) { - throw new NotImplementedException(); + this.schema.SerializeV2(writer); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index b7db6d6c..5c0b782a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Writers; - using System.Runtime.CompilerServices; public class AvroPrimitive : AvroFieldType { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index b837ac19..e314e887 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; - using System.Linq; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs index 1a5b74db..affc205a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System; using LEGO.AsyncAPI.Attributes; /// 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/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 458e3f14..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; 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/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 64e34c57..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 { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 43d15629..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; 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 { From 8ebe666aad80902cab82aa494e7228bd6df3221b Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 12:16:45 +0200 Subject: [PATCH 09/20] remove weird empty deserializer --- src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 6f0ef76c..8eb45f28 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -10,11 +10,6 @@ namespace LEGO.AsyncAPI.Readers using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.ParseNodes; - internal static partial class AsyncApiV2Deserializer - { - - } - /// /// Class containing logic to deserialize AsyncApi document into /// runtime AsyncApi object model. From 76567a6b17a7c49ad7cef3fded12cb447fd2c871 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 14:54:35 +0200 Subject: [PATCH 10/20] now with 100% more referenceable --- 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/SqsOperationBinding.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs | 2 +- ...AsyncApiUnsupportedSpecVersionException.cs | 2 +- .../IAsyncApiExternalReferenceReader.cs | 2 +- src/LEGO.AsyncAPI/AsyncApiVersion.cs | 2 +- .../Models/AsyncApiAvroSchemaPayload.cs | 9 ++++++ src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 6 ++-- .../Models/AsyncApiSchemaPayload.cs | 2 +- .../Models/AsyncApiSerializableExtensions.cs | 4 +-- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 32 +++++++++++++------ .../Models/Interfaces/IAsyncApiPayload.cs | 2 +- src/LEGO.AsyncAPI/Models/ParameterLocation.cs | 4 +-- src/LEGO.AsyncAPI/Models/ReferenceType.cs | 2 +- .../Writers/AsyncApiYamlWriter.cs | 2 +- test/LEGO.AsyncAPI.Tests/TestBase.cs | 4 +-- 18 files changed, 53 insertions(+), 32 deletions(-) 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/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.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/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/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index b4850d7d..786d9cd3 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -45,9 +45,18 @@ public AsyncApiAvroSchemaPayload(AvroSchema schema) /// public IList Fields { get => this.schema.Fields; set => this.schema.Fields = 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 void SerializeV2(IAsyncApiWriter writer) { this.schema.SerializeV2(writer); } + + public void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + this.schema.SerializeV2WithoutReference(writer); + } } } 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 index a0344702..19db07b2 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload, IAsyncApiReferenceable + public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload { private readonly AsyncApiSchema schema; 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/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index e314e887..51f10224 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -2,6 +2,7 @@ namespace LEGO.AsyncAPI.Models { + using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -9,7 +10,7 @@ namespace LEGO.AsyncAPI.Models /// /// Represents an Avro schema model (compatible with Avro 1.9.0). /// - public class AvroSchema : IAsyncApiSerializable + public class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable { /// /// The type of the schema. See Avro Schema Types. @@ -36,23 +37,34 @@ public class AvroSchema : IAsyncApiSerializable /// public IList Fields { get; set; } = new List(); + 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) { writer.WriteStartObject(); - // type writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); - - // name writer.WriteOptionalProperty("name", this.Name); - - // namespace writer.WriteOptionalProperty("namespace", this.Namespace); - - // doc writer.WriteOptionalProperty("doc", this.Doc); - - // fields writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); writer.WriteEndObject(); diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs index 1da08177..76112af9 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 + 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/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/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 55bf4a6d759ecc688639035286b711187dca1b59 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 22:51:59 +0200 Subject: [PATCH 11/20] add some missing fields --- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 9 ++++ src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 18 +++----- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 4 ++ src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 6 +++ .../Models/AvroSchema_Should.cs | 44 +++++++++++++++++++ 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index f302c923..e4c21946 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -11,14 +11,23 @@ public class AvroEnum : AvroFieldType public string Name { get; set; } + public string Doc { get; set; } + + public IList Aliases { get; set; } = new List(); + public IList Symbols { get; set; } = new List(); + public string Default { get; set; } + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + 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.WriteRequiredProperty("default", this.Default); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 1ae04bb1..86cffa0d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -4,6 +4,7 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; + using System.Collections.Generic; /// /// Represents a field within an Avro record schema. @@ -35,25 +36,20 @@ public class AvroField : IAsyncApiSerializable /// public string Order { get; set; } + /// + /// An array of strings, providing alternate names for this record (optional). + /// + public IList Aliases { get; set; } = new List(); + public void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); - - // name writer.WriteOptionalProperty("name", this.Name); - - // type writer.WriteOptionalObject("type", this.Type, (w, s) => s.SerializeV2(w)); - - // doc writer.WriteOptionalProperty("doc", this.Doc); - - // default writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s)); - - // order writer.WriteOptionalProperty("order", this.Order); - + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index f598c2ee..4e0c8ba4 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Writers; + using System.Collections.Generic; public class AvroFixed : AvroFieldType { @@ -10,6 +11,8 @@ public class AvroFixed : AvroFieldType public string Name { get; set; } + public IList Aliases { get; set; } = new List(); + public int Size { get; set; } public override void SerializeV2(IAsyncApiWriter writer) @@ -17,6 +20,7 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); writer.WriteRequiredProperty("size", this.Size); writer.WriteEndObject(); } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index bf0d87dc..20c40b00 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -11,6 +11,10 @@ public class AvroRecord : AvroFieldType public string Name { get; set; } + public string Doc { get; set; } + + public IList Aliases { get; set; } = new List(); + public IList Fields { get; set; } = new List(); public override void SerializeV2(IAsyncApiWriter writer) @@ -18,6 +22,8 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + 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)); writer.WriteEndObject(); } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 4efc0e38..e3aed195 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -172,6 +172,50 @@ public void SerializeV2_SerializesCorrectly() // Assert actual.Should() .BePlatformAgnosticEquivalentTo(expected); + }[Test] + public void test() + { + // Arrange + var input = """ + { + "type": "record", + "name": "SomeEvent", + "namespace": "my.namspace.for.event", + "example": { + "occurredOn": "2023-11-03T09:59:56.582908743Z", + "partnerId": "1", + "platformSource": "Brecht", + "countryCode": "DE" + }, + "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" + } + ] + } + """; + + // Act + var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + + // Assert } [Test] From de2963546a5678df02d0ab6bea2f2ffbb1f188cf Mon Sep 17 00:00:00 2001 From: VisualBean Date: Tue, 28 May 2024 22:58:39 +0200 Subject: [PATCH 12/20] schema comment --- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 130 +++++++++++++------- 1 file changed, 85 insertions(+), 45 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index 51f10224..439ac770 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -10,64 +10,104 @@ namespace LEGO.AsyncAPI.Models /// /// Represents an Avro schema model (compatible with Avro 1.9.0). /// + // #ToFix: Any type can be the main type so avroschema should be removed + /* + Record + + Fields: + name (string) + namespace (optional string) + aliases (optional array of strings) + fields (array of field objects, each with name, type, default, doc, order, and aliases) + Enum + + Fields: + name (string) + namespace (optional string) + aliases (optional array of strings) + symbols (array of strings) + default (optional string) + doc (optional string) + Array + + Fields: + items (type of the array elements) + Map + + Fields: + values (type of the map values) + Union + + Fields: + A list of potential types (each element is a valid Avro schema) + Fixed + + Fields: + name (string) + namespace (optional string) + aliases (optional array of strings) + size (integer specifying the number of bytes per value) + doc (optional string) + */ + public class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable - { - /// - /// The type of the schema. See Avro Schema Types. - /// - public AvroSchemaType Type { get; set; } +{ + /// + /// The type of the schema. See Avro Schema Types. + /// + public AvroSchemaType Type { get; set; } - /// - /// The name of the schema. Required for named types (e.g., record, enum, fixed). See Avro Names. - /// - public string Name { get; set; } + /// + /// The name of the schema. Required for named types (e.g., record, enum, fixed). 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; } + /// + /// 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; } + /// + /// Documentation for the schema. + /// + public string? Doc { get; set; } - /// - /// The list of fields in the schema. - /// - public IList Fields { get; set; } = new List(); + /// + /// The list of fields in the schema. + /// + public IList Fields { get; set; } = new List(); - public bool UnresolvedReference { get; set; } + public bool UnresolvedReference { get; set; } - public AsyncApiReference Reference { get; set; } + public AsyncApiReference Reference { get; set; } - public void SerializeV2(IAsyncApiWriter writer) + public void SerializeV2(IAsyncApiWriter writer) + { + if (writer is null) { - 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); + throw new ArgumentNullException(nameof(writer)); } - public void SerializeV2WithoutReference(IAsyncApiWriter writer) + if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference)) { - writer.WriteStartObject(); + this.Reference.SerializeV2(writer); + return; + } - writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); - writer.WriteOptionalProperty("name", this.Name); - writer.WriteOptionalProperty("namespace", this.Namespace); - writer.WriteOptionalProperty("doc", this.Doc); - writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); + this.SerializeV2WithoutReference(writer); + } - writer.WriteEndObject(); - } + public void SerializeV2WithoutReference(IAsyncApiWriter writer) + { + writer.WriteStartObject(); + + writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); + writer.WriteOptionalProperty("name", this.Name); + writer.WriteOptionalProperty("namespace", this.Namespace); + writer.WriteOptionalProperty("doc", this.Doc); + writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); + + writer.WriteEndObject(); } +} } \ No newline at end of file From 87b64da14dcecbb0ee07fc12b20ec9b66166e720 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Wed, 29 May 2024 23:22:26 +0200 Subject: [PATCH 13/20] implement metadata --- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 32 ++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index 20c40b00..bc0ed42d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -3,28 +3,58 @@ namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Writers; public class AvroRecord : AvroFieldType { public string Type { get; } = "record"; + /// + /// The name of the schema. Required for named types. See Avro Names. + /// public string Name { get; set; } - public string Doc { 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; } public IList Aliases { get; set; } = new List(); public IList Fields { get; set; } = new List(); + public IDictionary Metadata { get; set; } = new Dictionary(); + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + writer.WriteRequiredProperty("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 + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } From f2053e99f851acd1a74806bf2736b76c40712f6f Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Thu, 30 May 2024 10:51:28 +0200 Subject: [PATCH 14/20] WIP on alex/avro --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 52 ++-- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 5 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 2 +- .../Models/Avro/AvroFieldType.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 7 +- .../Models/Avro/AvroPrimitive.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 227 +++++++++--------- .../Models/Avro/AvroSchemaType.cs | 42 ++-- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 4 +- .../Models/AvroSchema_Should.cs | 4 +- 13 files changed, 185 insertions(+), 176 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 9e7b828b..2396af69 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -10,39 +10,39 @@ namespace LEGO.AsyncAPI.Readers public class AsyncApiAvroSchemaDeserializer { - private static readonly FixedFieldMap schemaFixedFields = new() - { - { "type", (a, n) => a.Type = n.GetScalarValue().GetEnumFromDisplayName() }, - { "name", (a, n) => a.Name = n.GetScalarValue() }, - { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, - { "doc", (a, n) => a.Doc = n.GetScalarValue() }, - { "fields", (a, n) => a.Fields = n.CreateList(LoadField) }, - }; - - public static AvroSchema LoadSchema(ParseNode node) - { - var mapNode = node.CheckMapNode("schema"); - var schema = new AvroSchema(); - - mapNode.ParseFields(ref schema, schemaFixedFields, null); - - return schema; - } + // private static readonly FixedFieldMap schemaFixedFields = new() + // { + // { "type", (a, n) => a.Type = n.GetScalarValue() }, + // { "name", (a, n) => a.Name = n.GetScalarValue() }, + // { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, + // { "doc", (a, n) => a.Doc = n.GetScalarValue() }, + // { "fields", (a, n) => a.Fields = n.CreateList(LoadField) }, + // }; + + // public static AvroSchema LoadSchema(ParseNode node) + // { + // var mapNode = node.CheckMapNode("schema"); + // var schema = new AvroSchema(); + // + // mapNode.ParseFields(ref schema, schemaFixedFields, null); + // + // return schema; + // } private static AvroField LoadField(ParseNode node) { var mapNode = node.CheckMapNode("field"); var field = new AvroField(); - + mapNode.ParseFields(ref field, fieldFixedFields, null); - + return field; } private static readonly FixedFieldMap fieldFixedFields = new() { { "name", (a, n) => a.Name = n.GetScalarValue() }, - { "type", (a, n) => a.Type = LoadFieldType(n) }, + { "type", (a, n) => a.Type = LoadSchema(n) }, { "doc", (a, n) => a.Doc = n.GetScalarValue() }, { "default", (a, n) => a.Default = n.CreateAny() }, { "order", (a, n) => a.Order = n.GetScalarValue() }, @@ -72,21 +72,21 @@ private static AvroField LoadField(ParseNode node) private static readonly FixedFieldMap arrayFixedFields = new() { { "type", (a, n) => { } }, - { "items", (a, n) => a.Items = LoadFieldType(n) }, + { "items", (a, n) => a.Items = LoadSchema(n) }, }; private static readonly FixedFieldMap mapFixedFields = new() { { "type", (a, n) => { } }, - { "values", (a, n) => a.Values = LoadFieldType(n) }, + { "values", (a, n) => a.Values = LoadSchema(n) }, }; private static readonly FixedFieldMap unionFixedFields = new() { - { "types", (a, n) => a.Types = n.CreateList(LoadFieldType) }, + { "types", (a, n) => a.Types = n.CreateList(LoadSchema) }, }; - private static AvroFieldType LoadFieldType(ParseNode node) + private static AvroSchema LoadSchema(ParseNode node) { if (node is ValueNode valueNode) { @@ -98,7 +98,7 @@ private static AvroFieldType LoadFieldType(ParseNode node) var union = new AvroUnion(); foreach (var item in node as ListNode) { - union.Types.Add(LoadFieldType(item)); + union.Types.Add(LoadSchema(item)); } return union; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 64fb1657..65f2cddd 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -4,11 +4,11 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Writers; - public class AvroArray : AvroFieldType + public class AvroArray : AvroSchema { public string Type { get; } = "array"; - public AvroFieldType Items { get; set; } + public AvroSchema Items { get; set; } public override void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index e4c21946..ae474e4f 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -5,11 +5,13 @@ namespace LEGO.AsyncAPI.Models using System.Collections.Generic; using LEGO.AsyncAPI.Writers; - public class AvroEnum : AvroFieldType + public class AvroEnum : AvroSchema { public string Type { get; } = "enum"; public string Name { get; set; } + + public string Namespace { get; set; } public string Doc { get; set; } @@ -24,6 +26,7 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + writer.WriteRequiredProperty("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)); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 86cffa0d..89be453b 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -19,7 +19,7 @@ public class AvroField : IAsyncApiSerializable /// /// The type of the field. Can be a primitive type, a complex type, or a union. /// - public AvroFieldType Type { get; set; } + public AvroSchema Type { get; set; } /// /// The documentation for the field. diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs index 8a53026a..76ba5a3b 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs @@ -5,9 +5,9 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public abstract class AvroFieldType : IAsyncApiSerializable + public abstract class AvroSchema : IAsyncApiSerializable { - public static implicit operator AvroFieldType(AvroPrimitiveType type) + public static implicit operator AvroSchema(AvroPrimitiveType type) { return new AvroPrimitive(type); } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 4e0c8ba4..35363425 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -5,11 +5,12 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Writers; using System.Collections.Generic; - public class AvroFixed : AvroFieldType + public class AvroFixed : AvroSchema { public string Type { get; } = "fixed"; public string Name { get; set; } + public string Namespaace { get; set; } public IList Aliases { get; set; } = new List(); @@ -20,6 +21,7 @@ public override void SerializeV2(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); + writer.WriteRequiredProperty("name", this.Namespaace); writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); writer.WriteRequiredProperty("size", this.Size); writer.WriteEndObject(); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 6c5398f7..fa99615d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -2,16 +2,19 @@ namespace LEGO.AsyncAPI.Models { + using System; + using System.Collections.Generic; using LEGO.AsyncAPI.Writers; - public class AvroMap : AvroFieldType + public class AvroMap : AvroSchema { public string Type { get; } = "map"; - public AvroFieldType Values { get; set; } + public IDictionary Values { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { + Convert.ToBoolean() writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredObject("values", this.Values, (w, f) => f.SerializeV2(w)); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 5c0b782a..7025a31d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -4,7 +4,7 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Writers; - public class AvroPrimitive : AvroFieldType + public class AvroPrimitive : AvroSchema { public AvroPrimitiveType Type { get; set; } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index bc0ed42d..c45936f6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.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 { @@ -6,7 +6,7 @@ namespace LEGO.AsyncAPI.Models using System.Linq; using LEGO.AsyncAPI.Writers; - public class AvroRecord : AvroFieldType + public class AvroRecord : AvroSchema { public string Type { get; } = "record"; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index 439ac770..3b29ad2a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -1,113 +1,114 @@ -// 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; - - /// - /// Represents an Avro schema model (compatible with Avro 1.9.0). - /// - // #ToFix: Any type can be the main type so avroschema should be removed - /* - Record - - Fields: - name (string) - namespace (optional string) - aliases (optional array of strings) - fields (array of field objects, each with name, type, default, doc, order, and aliases) - Enum - - Fields: - name (string) - namespace (optional string) - aliases (optional array of strings) - symbols (array of strings) - default (optional string) - doc (optional string) - Array - - Fields: - items (type of the array elements) - Map - - Fields: - values (type of the map values) - Union - - Fields: - A list of potential types (each element is a valid Avro schema) - Fixed - - Fields: - name (string) - namespace (optional string) - aliases (optional array of strings) - size (integer specifying the number of bytes per value) - doc (optional string) - */ - - public class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable -{ - /// - /// The type of the schema. See Avro Schema Types. - /// - public AvroSchemaType Type { get; set; } - - /// - /// The name of the schema. Required for named types (e.g., record, enum, fixed). 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; } - - /// - /// The list of fields in the schema. - /// - public IList Fields { get; set; } = new List(); - - 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) - { - writer.WriteStartObject(); - - writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); - writer.WriteOptionalProperty("name", this.Name); - writer.WriteOptionalProperty("namespace", this.Namespace); - writer.WriteOptionalProperty("doc", this.Doc); - writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); - - writer.WriteEndObject(); - } -} -} \ No newline at end of file +// // 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; +// +// /// +// /// Represents an Avro schema model (compatible with Avro 1.9.0). +// /// +// // #ToFix: Any type can be the main type so avroschema should be removed +// /* +// Record +// +// Fields: +// name (string) +// namespace (optional string) +// doc (optional string) +// aliases (optional array of strings) +// fields (array of field objects, each with name, type, default, doc, order, and aliases) +// +// Enum +// Fields: +// name (string) +// namespace (optional string) +// aliases (optional array of strings) +// symbols (array of strings) +// default (optional string) +// doc (optional string) +// +// Array +// Fields: +// items (type of the array elements) +// +// Map +// Fields: +// values (type of the map values) +// +// Union +// Fields: +// A list of potential types (each element is a valid Avro schema) +// +// Fixed +// Fields: +// name (string) +// namespace (optional string) +// aliases (optional array of strings) +// size (integer specifying the number of bytes per value) +// doc (optional string) +// */ +// +// public class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable +// { +// /// +// /// The type of the schema. See Avro Schema Types. +// /// +// public AvroSchemaType Type { get; set; } +// +// /// +// /// The name of the schema. Required for named types (e.g., record, enum, fixed). 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; } +// +// /// +// /// The list of fields in the schema. +// /// +// public IList Fields { get; set; } = new List(); +// +// 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) +// { +// writer.WriteStartObject(); +// +// writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); +// writer.WriteOptionalProperty("name", this.Name); +// writer.WriteOptionalProperty("namespace", this.Namespace); +// writer.WriteOptionalProperty("doc", this.Doc); +// writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); +// +// writer.WriteEndObject(); +// } +// } +// } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs index affc205a..df9b61d7 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs @@ -1,21 +1,21 @@ -// Copyright (c) The LEGO Group. All rights reserved. - -namespace LEGO.AsyncAPI.Models -{ - using LEGO.AsyncAPI.Attributes; - - /// - /// Enumeration of Avro schema types. See Avro Schemas. - /// - public enum AvroSchemaType - { - [Display("record")] - Record, - - [Display("enum")] - Enum, - - [Display("fixed")] - Fixed - } -} \ No newline at end of file +// // Copyright (c) The LEGO Group. All rights reserved. +// +// namespace LEGO.AsyncAPI.Models +// { +// using LEGO.AsyncAPI.Attributes; +// +// /// +// /// Enumeration of Avro schema types. See Avro Schemas. +// /// +// public enum AvroSchemaType +// { +// [Display("record")] +// Record, +// +// [Display("enum")] +// Enum, +// +// [Display("fixed")] +// Fixed +// } +// } \ 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 57111d98..7949dd4a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -5,9 +5,9 @@ namespace LEGO.AsyncAPI.Models using System.Collections.Generic; using LEGO.AsyncAPI.Writers; - public class AvroUnion : AvroFieldType + public class AvroUnion : AvroSchema { - public IList Types { get; set; } = new List(); + public IList Types { get; set; } = new List(); public override void SerializeV2(IAsyncApiWriter writer) { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index e3aed195..4f5e3e3f 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -147,7 +147,7 @@ public void SerializeV2_SerializesCorrectly() Name = "contact", Type = new AvroUnion { - Types = new List + Types = new List { AvroPrimitiveType.Null, new AvroRecord @@ -355,7 +355,7 @@ public void ReadFragment_DeserializesCorrectly() Name = "contact", Type = new AvroUnion { - Types = new List + Types = new List { AvroPrimitiveType.Null, new AvroRecord From 799cf93519fd1a8b99b5d7e61a739f5e9ee04eff Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Thu, 30 May 2024 12:51:01 +0200 Subject: [PATCH 15/20] mas --- .../Models/AsyncApiAvroSchemaPayload.cs | 53 +++++++------------ src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 24 ++++++++- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 27 ++++++++-- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 21 ++++++++ .../Models/Avro/AvroFieldType.cs | 2 + src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 28 ++++++++-- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 28 ++++++++-- .../Models/Avro/AvroPrimitive.cs | 32 +++++++++-- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 15 ++++-- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 24 +++++++++ 10 files changed, 203 insertions(+), 51 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index 786d9cd3..bbd28005 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -8,55 +8,40 @@ namespace LEGO.AsyncAPI.Models public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload { - private AvroSchema schema; + public AvroSchema Schema { get; set; } - public AsyncApiAvroSchemaPayload() + public AsyncApiAvroSchemaPayload(AvroSchema schema) { - this.schema = new AvroSchema(); + this.Schema = schema; } - public AsyncApiAvroSchemaPayload(AvroSchema schema) + public AsyncApiAvroSchemaPayload() { - this.schema = schema; } - /// - /// The type of the schema. See Avro Schema Types. - /// - public AvroSchemaType Type { get => this.schema.Type; set => this.schema.Type = value; } - - /// - /// The name of the schema. Required for named types (e.g., record, enum, fixed). See Avro Names. - /// - public string Name { get => this.schema.Name; set => this.schema.Name = value; } - - /// - /// The namespace of the schema. Useful for named types to avoid name conflicts. - /// - public string? Namespace { get => this.schema.Namespace; set => this.schema.Namespace = value; } - - /// - /// Documentation for the schema. - /// - public string? Doc { get => this.schema.Doc; set => this.schema.Doc = value; } - - /// - /// The list of fields in the schema. - /// - public IList Fields { get => this.schema.Fields; set => this.schema.Fields = value; } - - public bool UnresolvedReference { get => this.schema.UnresolvedReference; set => this.schema.UnresolvedReference = value; } + public bool UnresolvedReference { get; set; } - public AsyncApiReference Reference { get => this.schema.Reference; set => this.schema.Reference = value; } + public AsyncApiReference Reference { get; set; } public void SerializeV2(IAsyncApiWriter writer) { - this.schema.SerializeV2(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.SerializeV2WithoutReference(writer); + this.Schema.SerializeV2(writer); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 65f2cddd..74fa6911 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -2,19 +2,41 @@ namespace LEGO.AsyncAPI.Models { + using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Writers; public class AvroArray : AvroSchema { - public string Type { get; } = "array"; + public override string Type { get; } = "array"; public AvroSchema Items { get; set; } + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); + public override void SerializeV2(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 + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index ae474e4f..b43cff70 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -3,14 +3,15 @@ namespace LEGO.AsyncAPI.Models { using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Writers; public class AvroEnum : AvroSchema { - public string Type { get; } = "enum"; + public override string Type { get; } = "enum"; public string Name { get; set; } - + public string Namespace { get; set; } public string Doc { get; set; } @@ -21,16 +22,36 @@ public class AvroEnum : AvroSchema public string Default { get; set; } + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WriteRequiredProperty("namespace", this.Namespace); + 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.WriteRequiredProperty("default", this.Default); + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 89be453b..5ce87047 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -5,6 +5,7 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; using System.Collections.Generic; + using System.Linq; /// /// Represents a field within an Avro record schema. @@ -41,6 +42,11 @@ public class AvroField : IAsyncApiSerializable /// 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(); @@ -50,6 +56,21 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s)); writer.WriteOptionalProperty("order", this.Order); 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 + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs index 76ba5a3b..d967e41f 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs @@ -7,6 +7,8 @@ namespace LEGO.AsyncAPI.Models public abstract class AvroSchema : IAsyncApiSerializable { + public abstract string Type { get; } + public static implicit operator AvroSchema(AvroPrimitiveType type) { return new AvroPrimitive(type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 35363425..9c35c28d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -4,26 +4,48 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Writers; using System.Collections.Generic; + using System.Linq; public class AvroFixed : AvroSchema { - public string Type { get; } = "fixed"; + public override string Type { get; } = "fixed"; public string Name { get; set; } - public string Namespaace { get; set; } + + public string? Namespaace { get; set; } public IList Aliases { get; set; } = new List(); public int Size { get; set; } + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); + public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WriteRequiredProperty("name", this.Namespaace); + writer.WriteOptionalProperty("namespace", this.Namespaace); 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 + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index fa99615d..147aed81 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -4,20 +4,40 @@ namespace LEGO.AsyncAPI.Models { using System; using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Writers; public class AvroMap : AvroSchema { - public string Type { get; } = "map"; + public override string Type { get; } = "map"; - public IDictionary Values { get; set; } = new Dictionary(); + public AvroPrimitiveType Values { get; set; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { - Convert.ToBoolean() writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); - writer.WriteRequiredObject("values", this.Values, (w, f) => f.SerializeV2(w)); + 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 + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 7025a31d..9d24616e 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -2,20 +2,46 @@ namespace LEGO.AsyncAPI.Models { + using System.Collections.Generic; + using System.Linq; using LEGO.AsyncAPI.Writers; public class AvroPrimitive : AvroSchema { - public AvroPrimitiveType Type { get; set; } + public override string Type { get; } + + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); public AvroPrimitive(AvroPrimitiveType type) { - this.Type = type; + this.Type = type.GetDisplayName(); + } + + public AvroPrimitive() + { } public override void SerializeV2(IAsyncApiWriter writer) { - writer.WriteValue(this.Type.GetDisplayName()); + 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 + { + item.Value.SerializeV2(writer); + } + } + } } } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index c45936f6..fe3594c3 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -8,7 +8,7 @@ namespace LEGO.AsyncAPI.Models public class AvroRecord : AvroSchema { - public string Type { get; } = "record"; + public override string Type { get; } = "record"; /// /// The name of the schema. Required for named types. See Avro Names. @@ -25,18 +25,27 @@ public class AvroRecord : AvroSchema /// public string? Doc { get; set; } + /// + /// + /// public IList Aliases { get; set; } = new List(); + /// + /// + /// public IList Fields { get; set; } = new List(); - public IDictionary Metadata { get; set; } = new Dictionary(); + /// + /// A map of properties not in the schema, but added as additional metadata. + /// + public IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WriteRequiredProperty("namespace", this.Namespace); + 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)); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs index 7949dd4a..8b76a463 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -3,12 +3,20 @@ 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"; + public IList Types { 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 override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartArray(); @@ -16,6 +24,22 @@ public override void SerializeV2(IAsyncApiWriter writer) { type.SerializeV2(writer); } + + if (this.Metadata.Any()) + { + foreach (var item in this.Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNull(); + } + else + { + item.Value.SerializeV2(writer); + } + } + } writer.WriteEndArray(); } } From 1dd246806d75a3c759799f4355bbb904943052f3 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 31 May 2024 12:45:49 +0200 Subject: [PATCH 16/20] now with more read write --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 125 +++++++++++------- .../Models/AsyncApiAvroSchemaPayload.cs | 1 - src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 6 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 4 +- .../Models/Avro/AvroFieldType.cs | 8 +- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 8 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 4 +- .../Models/Avro/AvroPrimitive.cs | 4 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 8 +- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 4 +- .../Models/AvroSchema_Should.cs | 93 +++++++------ 12 files changed, 153 insertions(+), 116 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index 2396af69..db1e45e0 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 System.Collections; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.ParseNodes; @@ -10,83 +11,106 @@ namespace LEGO.AsyncAPI.Readers public class AsyncApiAvroSchemaDeserializer { - // private static readonly FixedFieldMap schemaFixedFields = new() - // { - // { "type", (a, n) => a.Type = n.GetScalarValue() }, - // { "name", (a, n) => a.Name = n.GetScalarValue() }, - // { "namespace", (a, n) => a.Namespace = n.GetScalarValue() }, - // { "doc", (a, n) => a.Doc = n.GetScalarValue() }, - // { "fields", (a, n) => a.Fields = n.CreateList(LoadField) }, - // }; - - // public static AvroSchema LoadSchema(ParseNode node) - // { - // var mapNode = node.CheckMapNode("schema"); - // var schema = new AvroSchema(); - // - // mapNode.ParseFields(ref schema, schemaFixedFields, null); - // - // return schema; - // } - - private static AvroField LoadField(ParseNode node) - { - var mapNode = node.CheckMapNode("field"); - var field = new AvroField(); - - mapNode.ParseFields(ref field, fieldFixedFields, null); - - return field; - } - - private static readonly FixedFieldMap fieldFixedFields = new() + 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() }, }; - private static readonly FixedFieldMap recordFixedFields = new() + 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() + 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() + 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() + private static readonly FixedFieldMap ArrayFixedFields = new() { { "type", (a, n) => { } }, { "items", (a, n) => a.Items = LoadSchema(n) }, }; - private static readonly FixedFieldMap mapFixedFields = new() + private static readonly FixedFieldMap MapFixedFields = new() { { "type", (a, n) => { } }, - { "values", (a, n) => a.Values = LoadSchema(n) }, + { "values", (a, n) => a.Values = n.GetScalarValue().GetEnumFromDisplayName() }, }; - private static readonly FixedFieldMap unionFixedFields = new() + private static readonly FixedFieldMap UnionFixedFields = new() { { "types", (a, n) => a.Types = n.CreateList(LoadSchema) }, }; - private static AvroSchema LoadSchema(ParseNode node) + 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) { @@ -106,33 +130,33 @@ private static AvroSchema LoadSchema(ParseNode node) if (node is MapNode mapNode) { - var type = mapNode["type"].Value?.GetScalarValue(); + var type = mapNode["type"]?.Value.GetScalarValue(); switch (type) { case "record": var record = new AvroRecord(); - mapNode.ParseFields(ref record, recordFixedFields, null); + mapNode.ParseFields(ref record, RecordFixedFields, RecordMetadataPatternFields); return record; case "enum": var @enum = new AvroEnum(); - mapNode.ParseFields(ref @enum, enumFixedFields, null); + mapNode.ParseFields(ref @enum, EnumFixedFields, EnumMetadataPatternFields); return @enum; case "fixed": var @fixed = new AvroFixed(); - mapNode.ParseFields(ref @fixed, fixedFixedFields, null); + mapNode.ParseFields(ref @fixed, FixedFixedFields, FixedMetadataPatternFields); return @fixed; case "array": var array = new AvroArray(); - mapNode.ParseFields(ref array, arrayFixedFields, null); + mapNode.ParseFields(ref array, ArrayFixedFields, ArrayMetadataPatternFields); return array; case "map": var map = new AvroMap(); - mapNode.ParseFields(ref map, mapFixedFields, null); + mapNode.ParseFields(ref map, MapFixedFields, MapMetadataPatternFields); return map; case "union": var union = new AvroUnion(); - mapNode.ParseFields(ref union, unionFixedFields, null); + mapNode.ParseFields(ref union, UnionFixedFields, UnionMetadataPatternFields); return union; default: throw new InvalidOperationException($"Unsupported type: {type}"); @@ -141,5 +165,16 @@ private static AvroSchema LoadSchema(ParseNode node) 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/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index bbd28005..1f8e8175 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -2,7 +2,6 @@ namespace LEGO.AsyncAPI.Models { - using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 74fa6911..ca99b6f9 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -15,7 +15,7 @@ public class AvroArray : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { @@ -33,7 +33,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index b43cff70..7a019d51 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -25,7 +25,7 @@ public class AvroEnum : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { @@ -36,7 +36,7 @@ public override void SerializeV2(IAsyncApiWriter writer) 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.WriteRequiredProperty("default", this.Default); + writer.WriteOptionalProperty("default", this.Default); if (this.Metadata.Any()) { foreach (var item in this.Metadata) @@ -48,7 +48,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 5ce87047..17eb414a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -45,7 +45,7 @@ public class AvroField : IAsyncApiSerializable /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public IDictionary Metadata { get; set; } = new Dictionary(); public void SerializeV2(IAsyncApiWriter writer) { @@ -67,7 +67,7 @@ public void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs index d967e41f..4366c5e8 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs @@ -4,11 +4,17 @@ namespace LEGO.AsyncAPI.Models { using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; + using System.Collections.Generic; public abstract class AvroSchema : IAsyncApiSerializable { 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 AvroSchema(AvroPrimitiveType type) { return new AvroPrimitive(type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index 9c35c28d..c1d1e637 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -12,7 +12,7 @@ public class AvroFixed : AvroSchema public string Name { get; set; } - public string? Namespaace { get; set; } + public string Namespace { get; set; } public IList Aliases { get; set; } = new List(); @@ -21,14 +21,14 @@ public class AvroFixed : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); writer.WriteRequiredProperty("name", this.Name); - writer.WriteOptionalProperty("namespace", this.Namespaace); + writer.WriteOptionalProperty("namespace", this.Namespace); writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s)); writer.WriteRequiredProperty("size", this.Size); if (this.Metadata.Any()) @@ -42,7 +42,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index 147aed81..dcda89f4 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -16,7 +16,7 @@ public class AvroMap : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { @@ -34,7 +34,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs index 9d24616e..6a34dbeb 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -13,7 +13,7 @@ public class AvroPrimitive : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public AvroPrimitive(AvroPrimitiveType type) { @@ -38,7 +38,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index fe3594c3..1d3b63c6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -18,12 +18,12 @@ public class AvroRecord : AvroSchema /// /// The namespace of the schema. Useful for named types to avoid name conflicts. /// - public string? Namespace { get; set; } + public string Namespace { get; set; } /// /// Documentation for the schema. /// - public string? Doc { get; set; } + public string Doc { get; set; } /// /// @@ -38,7 +38,7 @@ public class AvroRecord : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { @@ -60,7 +60,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs index 8b76a463..41712d3a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -15,7 +15,7 @@ public class AvroUnion : AvroSchema /// /// A map of properties not in the schema, but added as additional metadata. /// - public IDictionary Metadata { get; set; } = new Dictionary(); + public override IDictionary Metadata { get; set; } = new Dictionary(); public override void SerializeV2(IAsyncApiWriter writer) { @@ -36,7 +36,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } else { - item.Value.SerializeV2(writer); + writer.WriteAny(item.Value); } } } diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 4f5e3e3f..b1d17f4d 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -10,6 +10,49 @@ namespace LEGO.AsyncAPI.Tests.Models public class AvroSchema_Should { + [Test] + public void Deserialize_WithMetadata_CreatesMetadata() + { + var input = + """ + { + "type": "record", + "name": "SomeEvent", + "namespace": "my.namspace.for.event", + "example": { + "occurredOn": "2023-11-03T09:59:56.582908743Z", + "partnerId": "1", + "platformSource": "Brecht", + "countryCode": "DE" + }, + "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" + } + ] + } + """; + var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag); + model.Metadata.Should().HaveCount(1); + } + [Test] public void SerializeV2_SerializesCorrectly() { @@ -74,9 +117,8 @@ public void SerializeV2_SerializesCorrectly() doc: 'The contact information of the user, which can be either null or a phone number.' """; - var schema = new AvroSchema + var schema = new AvroRecord { - Type = AvroSchemaType.Record, Name = "User", Namespace = "com.example", Fields = new List @@ -172,50 +214,6 @@ public void SerializeV2_SerializesCorrectly() // Assert actual.Should() .BePlatformAgnosticEquivalentTo(expected); - }[Test] - public void test() - { - // Arrange - var input = """ - { - "type": "record", - "name": "SomeEvent", - "namespace": "my.namspace.for.event", - "example": { - "occurredOn": "2023-11-03T09:59:56.582908743Z", - "partnerId": "1", - "platformSource": "Brecht", - "countryCode": "DE" - }, - "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" - } - ] - } - """; - - // Act - var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic); - - // Assert } [Test] @@ -282,9 +280,8 @@ public void ReadFragment_DeserializesCorrectly() doc: 'The contact information of the user, which can be either null or a phone number.' """; - var expected = new AvroSchema + var expected = new AvroRecord { - Type = AvroSchemaType.Record, Name = "User", Namespace = "com.example", Fields = new List From 4f53f13750ea06fd9c2b6a74cee785a92ce24dd7 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 31 May 2024 12:55:31 +0200 Subject: [PATCH 17/20] minor fixes and docs --- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 4 + src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 19 +++ src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 7 +- .../Models/Avro/AvroFieldType.cs | 25 ---- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 16 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 1 + src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 3 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 139 ++++-------------- .../Models/Avro/AvroSchemaType.cs | 21 --- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 4 + .../Services/AsyncApiReferenceResolver.cs | 1 + 11 files changed, 75 insertions(+), 165 deletions(-) delete mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs delete mode 100644 src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index ca99b6f9..95a94e91 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -10,6 +10,9 @@ public class AvroArray : AvroSchema { public override string Type { get; } = "array"; + /// + /// The schema of the array's items. + /// public AvroSchema Items { get; set; } /// @@ -37,6 +40,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs index 7a019d51..7a694f41 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -10,16 +10,34 @@ 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; } /// @@ -52,6 +70,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs index 17eb414a..69f596a6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -2,10 +2,10 @@ namespace LEGO.AsyncAPI.Models { - using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; using System.Collections.Generic; using System.Linq; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; /// /// Represents a field within an Avro record schema. @@ -38,7 +38,7 @@ public class AvroField : IAsyncApiSerializable public string Order { get; set; } /// - /// An array of strings, providing alternate names for this record (optional). + /// Alternate names for this record (optional). /// public IList Aliases { get; set; } = new List(); @@ -71,6 +71,7 @@ public void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs deleted file mode 100644 index 4366c5e8..00000000 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFieldType.cs +++ /dev/null @@ -1,25 +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.Collections.Generic; - - public abstract class AvroSchema : IAsyncApiSerializable - { - 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 AvroSchema(AvroPrimitiveType type) - { - return new AvroPrimitive(type); - } - - public abstract void SerializeV2(IAsyncApiWriter writer); - } -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index c1d1e637..bced9fa7 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -2,20 +2,33 @@ namespace LEGO.AsyncAPI.Models { - using LEGO.AsyncAPI.Writers; 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; } /// @@ -46,6 +59,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs index dcda89f4..879959f6 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -38,6 +38,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index 1d3b63c6..60c4d73f 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -26,7 +26,7 @@ public class AvroRecord : AvroSchema public string Doc { get; set; } /// - /// + /// Alternate names for this record. /// public IList Aliases { get; set; } = new List(); @@ -64,6 +64,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndObject(); } } diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index 3b29ad2a..1d1f0a8f 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -1,114 +1,25 @@ -// // 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; -// -// /// -// /// Represents an Avro schema model (compatible with Avro 1.9.0). -// /// -// // #ToFix: Any type can be the main type so avroschema should be removed -// /* -// Record -// -// Fields: -// name (string) -// namespace (optional string) -// doc (optional string) -// aliases (optional array of strings) -// fields (array of field objects, each with name, type, default, doc, order, and aliases) -// -// Enum -// Fields: -// name (string) -// namespace (optional string) -// aliases (optional array of strings) -// symbols (array of strings) -// default (optional string) -// doc (optional string) -// -// Array -// Fields: -// items (type of the array elements) -// -// Map -// Fields: -// values (type of the map values) -// -// Union -// Fields: -// A list of potential types (each element is a valid Avro schema) -// -// Fixed -// Fields: -// name (string) -// namespace (optional string) -// aliases (optional array of strings) -// size (integer specifying the number of bytes per value) -// doc (optional string) -// */ -// -// public class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable -// { -// /// -// /// The type of the schema. See Avro Schema Types. -// /// -// public AvroSchemaType Type { get; set; } -// -// /// -// /// The name of the schema. Required for named types (e.g., record, enum, fixed). 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; } -// -// /// -// /// The list of fields in the schema. -// /// -// public IList Fields { get; set; } = new List(); -// -// 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) -// { -// writer.WriteStartObject(); -// -// writer.WriteOptionalProperty(AsyncApiConstants.Type, this.Type.GetDisplayName()); -// writer.WriteOptionalProperty("name", this.Name); -// writer.WriteOptionalProperty("namespace", this.Namespace); -// writer.WriteOptionalProperty("doc", this.Doc); -// writer.WriteOptionalCollection("fields", this.Fields, (w, f) => f.SerializeV2(w)); -// -// writer.WriteEndObject(); -// } -// } -// } \ No newline at end of file +// 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 abstract class AvroSchema : IAsyncApiSerializable + { + 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 AvroSchema(AvroPrimitiveType type) + { + return new AvroPrimitive(type); + } + + public abstract void SerializeV2(IAsyncApiWriter writer); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs deleted file mode 100644 index df9b61d7..00000000 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchemaType.cs +++ /dev/null @@ -1,21 +0,0 @@ -// // Copyright (c) The LEGO Group. All rights reserved. -// -// namespace LEGO.AsyncAPI.Models -// { -// using LEGO.AsyncAPI.Attributes; -// -// /// -// /// Enumeration of Avro schema types. See Avro Schemas. -// /// -// public enum AvroSchemaType -// { -// [Display("record")] -// Record, -// -// [Display("enum")] -// Enum, -// -// [Display("fixed")] -// Fixed -// } -// } \ 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 41712d3a..2cb72fd1 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -10,6 +10,9 @@ public class AvroUnion : AvroSchema { public override string Type { get; } = "map"; + /// + /// The types in this union. + /// public IList Types { get; set; } = new List(); /// @@ -40,6 +43,7 @@ public override void SerializeV2(IAsyncApiWriter writer) } } } + writer.WriteEndArray(); } } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index 32cd435d..c37c2804 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -92,6 +92,7 @@ public override void Visit(AsyncApiMessage message) { 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); From 453db2919a8f84c9be035c1a93856534321a46b1 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 3 Jun 2024 12:18:12 +0200 Subject: [PATCH 18/20] mas avro goodness --- .../V2/AsyncApiAvroSchemaDeserializer.cs | 14 +++- .../V2/AsyncApiMessageDeserializer.cs | 4 + .../Models/AsyncApiAvroSchemaPayload.cs | 11 ++- src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroField.cs | 22 +++++- src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs | 2 +- .../Models/Avro/AvroPrimitive.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs | 25 ++++++- src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs | 2 +- .../Models/AsyncApiMessage_Should.cs | 74 ++++++++++++++++++- .../Models/AvroSchema_Should.cs | 4 +- 14 files changed, 150 insertions(+), 18 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs index db1e45e0..013ec856 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs @@ -3,7 +3,6 @@ namespace LEGO.AsyncAPI.Readers { using System; - using System.Collections; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers.Exceptions; using LEGO.AsyncAPI.Readers.ParseNodes; @@ -18,7 +17,7 @@ public class AsyncApiAvroSchemaDeserializer { "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() }, + { "order", (a, n) => a.Order = n.GetScalarValue().GetEnumFromDisplayName() }, }; private static readonly FixedFieldMap RecordFixedFields = new() @@ -130,6 +129,17 @@ public static AvroSchema LoadSchema(ParseNode node) 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) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs index 8eb45f28..2670d88a 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs @@ -77,6 +77,7 @@ public static IAsyncApiMessagePayload LoadAvroPayload(ParseNode n) private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) { + if (n == null) { return null; @@ -107,6 +108,9 @@ private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format) 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", }; diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs index 1f8e8175..596115ba 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs @@ -18,9 +18,16 @@ public AsyncApiAvroSchemaPayload() { } - public bool UnresolvedReference { get; set; } + 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; set; } + public AsyncApiReference Reference { get => this.Schema.Reference; set => this.Schema.Reference = value; } public void SerializeV2(IAsyncApiWriter writer) { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs index 95a94e91..0958fa3d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs @@ -20,7 +20,7 @@ public class AvroArray : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(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 7a694f41..dbfbb627 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs @@ -45,7 +45,7 @@ public class AvroEnum : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(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 69f596a6..6e8f9ed8 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs @@ -4,9 +4,23 @@ 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. /// @@ -35,7 +49,7 @@ public class AvroField : IAsyncApiSerializable /// /// The order of the field, can be 'ascending', 'descending', or 'ignore'. /// - public string Order { get; set; } + public AvroFieldOrder Order { get; set; } /// /// Alternate names for this record (optional). @@ -54,7 +68,11 @@ public void SerializeV2(IAsyncApiWriter writer) 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.WriteOptionalProperty("order", this.Order); + 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()) { diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs index bced9fa7..241e8c1e 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs @@ -36,7 +36,7 @@ public class AvroFixed : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(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 879959f6..c66d476b 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs @@ -18,7 +18,7 @@ public class AvroMap : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(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 6a34dbeb..d7526344 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs @@ -24,7 +24,7 @@ public AvroPrimitive() { } - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(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 60c4d73f..f7582d7a 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -40,7 +40,7 @@ public class AvroRecord : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) { writer.WriteStartObject(); writer.WriteOptionalProperty("type", this.Type); diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs index 1d1f0a8f..fce1df2d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs @@ -2,11 +2,12 @@ namespace LEGO.AsyncAPI.Models { + using System; using System.Collections.Generic; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public abstract class AvroSchema : IAsyncApiSerializable + public abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable { public abstract string Type { get; } @@ -15,11 +16,31 @@ public abstract class AvroSchema : IAsyncApiSerializable /// 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 abstract void SerializeV2(IAsyncApiWriter writer); + 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 2cb72fd1..5762935d 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs @@ -20,7 +20,7 @@ public class AvroUnion : AvroSchema /// public override IDictionary Metadata { get; set; } = new Dictionary(); - public override void SerializeV2(IAsyncApiWriter writer) + public override void SerializeV2WithoutReference(IAsyncApiWriter writer) { writer.WriteStartArray(); foreach (var type in this.Types) diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs index aed6fd77..538664a8 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs @@ -129,7 +129,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat() } [Test] - public void AsyncApiMessage_WithSchemaFormat_Serializes() + public void AsyncApiMessage_WithJsonSchemaFormat_Serializes() { // Arrange var expected = @@ -168,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() { diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index b1d17f4d..8be9f67e 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -129,7 +129,7 @@ public void SerializeV2_SerializesCorrectly() Type = AvroPrimitiveType.String, Doc = "The username of the user.", Default = new AsyncApiAny("guest"), - Order = "ascending", + Order = AvroFieldOrder.Ascending, }, new AvroField { @@ -292,7 +292,7 @@ public void ReadFragment_DeserializesCorrectly() Type = AvroPrimitiveType.String, Doc = "The username of the user.", Default = new AsyncApiAny("guest"), - Order = "ascending", + Order = AvroFieldOrder.Ascending, }, new AvroField { From 8817137cab3839a4c89229e7951c7d619e319bfd Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 3 Jun 2024 12:30:47 +0200 Subject: [PATCH 19/20] add additional testing for the metadata --- .../Models/AvroSchema_Should.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs index 8be9f67e..2b5b5b0e 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs @@ -19,12 +19,6 @@ public void Deserialize_WithMetadata_CreatesMetadata() "type": "record", "name": "SomeEvent", "namespace": "my.namspace.for.event", - "example": { - "occurredOn": "2023-11-03T09:59:56.582908743Z", - "partnerId": "1", - "platformSource": "Brecht", - "countryCode": "DE" - }, "fields": [ { "name": "countryCode", @@ -46,11 +40,23 @@ public void Deserialize_WithMetadata_CreatesMetadata() "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] From 8c88b5eaf430d5f604023dda77966e22f6137df2 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 3 Jun 2024 12:33:49 +0200 Subject: [PATCH 20/20] add missing docs for 'fields' --- src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs index f7582d7a..04642f5c 100644 --- a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs +++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs @@ -31,7 +31,7 @@ public class AvroRecord : AvroSchema public IList Aliases { get; set; } = new List(); /// - /// + /// A list of fields contained within this record. /// public IList Fields { get; set; } = new List();