From c96dab3ce9e10ae132b6e97d98f86e98604d42a1 Mon Sep 17 00:00:00 2001 From: VisualBean Date: Thu, 23 May 2024 14:01:11 +0200 Subject: [PATCH] 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); + } + } +}