diff --git a/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQChannelBinding.cs b/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQChannelBinding.cs
new file mode 100644
index 0000000..7c76aa0
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQChannelBinding.cs
@@ -0,0 +1,52 @@
+namespace ByteBard.AsyncAPI.Bindings.AnypointMQ
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding for an Anypoint MQ destination.
+ public class AnypointMQChannelBinding : ChannelBinding
+ {
+ public string Destination { get; set; }
+
+ public string DestinationType { get; set; }
+
+ public override string BindingKey => "anypointmq";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "destination", (a, n) => { a.Destination = n.GetScalarValue(); } },
+ { "destinationType", (a, n) => { a.DestinationType = n.GetScalarValue(); } },
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the Anypoint MQ channel binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+
+ ///
+ /// Writes the Anypoint MQ channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the Anypoint MQ channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("destination", this.Destination);
+ writer.WriteOptionalProperty("destinationType", this.DestinationType);
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQMessageBinding.cs b/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQMessageBinding.cs
new file mode 100644
index 0000000..82cf7eb
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/AnypointMQ/AnypointMQMessageBinding.cs
@@ -0,0 +1,58 @@
+namespace ByteBard.AsyncAPI.Bindings.AnypointMQ
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding for an Anypoint MQ message.
+ public class AnypointMQMessageBinding : MessageBinding
+ {
+ /// Message headers described by a schema or schema reference.
+ public AsyncApiJsonSchema Headers { get; set; }
+
+ public override string BindingKey => "anypointmq";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "headers", (a, n) => { a.Headers = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the Anypoint MQ message binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalObject("headers", this.Headers, (w, h) => h.SerializeV2(w));
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+
+ ///
+ /// Writes the Anypoint MQ message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the Anypoint MQ message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalObject("headers", this.Headers, (w, h) => h.SerializeV3(w));
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/BindingHelpers.cs b/src/ByteBard.AsyncAPI.Bindings/BindingHelpers.cs
index 219e79e..acff5b1 100644
--- a/src/ByteBard.AsyncAPI.Bindings/BindingHelpers.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/BindingHelpers.cs
@@ -1,11 +1,21 @@
namespace ByteBard.AsyncAPI.Bindings
{
+ using System;
+ using System.Collections.Generic;
using ByteBard.AsyncAPI.Models.Interfaces;
using ByteBard.AsyncAPI.Readers;
using ByteBard.AsyncAPI.Readers.ParseNodes;
public static class BindingHelpers
{
+ ///
+ /// Parses a nested object using the supplied field handlers.
+ ///
+ /// The nested object to parse.
+ /// Handlers for recognized property names; other properties follow the reader's unmapped-member setting.
+ /// A new instance populated from recognized fields, which may be incomplete when field parsing reports errors.
+ /// Field-level reader and AsyncAPI exceptions are recorded as diagnostics; other handler exceptions propagate.
+ /// is not a map.
public static T ParseMap(this ParseNode node, FixedFieldMap fixedFieldMap)
where T : new()
{
@@ -16,10 +26,11 @@ public static T ParseMap(this ParseNode node, FixedFieldMap fixedFieldMap)
}
var instance = new T();
+ var emptyPatternFields = new Dictionary, Action>();
foreach (var propertyNode in mapNode)
{
- propertyNode.ParseField(instance, fixedFieldMap, null);
+ propertyNode.ParseField(instance, fixedFieldMap, emptyPatternFields);
}
return instance;
@@ -44,4 +55,4 @@ public static T ParseMapWithExtensions(this ParseNode node, FixedFieldMap
return instance;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/BindingsCollection.cs b/src/ByteBard.AsyncAPI.Bindings/BindingsCollection.cs
index 5801c24..4c5ff39 100644
--- a/src/ByteBard.AsyncAPI.Bindings/BindingsCollection.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/BindingsCollection.cs
@@ -1,13 +1,21 @@
-namespace ByteBard.AsyncAPI.Bindings
+namespace ByteBard.AsyncAPI.Bindings
{
using System;
using System.Collections.Generic;
using ByteBard.AsyncAPI.Bindings.AMQP;
+ using ByteBard.AsyncAPI.Bindings.AnypointMQ;
+ using ByteBard.AsyncAPI.Bindings.GooglePubSub;
using ByteBard.AsyncAPI.Bindings.Http;
+ using ByteBard.AsyncAPI.Bindings.IBMMQ;
+ using ByteBard.AsyncAPI.Bindings.JMS;
using ByteBard.AsyncAPI.Bindings.Kafka;
using ByteBard.AsyncAPI.Bindings.MQTT;
+ using ByteBard.AsyncAPI.Bindings.MQTT5;
+ using ByteBard.AsyncAPI.Bindings.NATS;
using ByteBard.AsyncAPI.Bindings.Pulsar;
+ using ByteBard.AsyncAPI.Bindings.ROS2;
using ByteBard.AsyncAPI.Bindings.Sns;
+ using ByteBard.AsyncAPI.Bindings.Solace;
using ByteBard.AsyncAPI.Bindings.Sqs;
using ByteBard.AsyncAPI.Bindings.WebSockets;
using ByteBard.AsyncAPI.Models.Interfaces;
@@ -15,51 +23,37 @@
public static class BindingsCollection
{
- public static TCollection Add(
- this TCollection destination,
- IEnumerable source)
- where TCollection : ICollection
+ ///
+ /// Adds the source items to the destination collection.
+ ///
+ /// The same destination collection, after adding the items.
+ /// Enumeration and collection errors propagate; items already added are not rolled back.
+ /// or is null.
+ /// The destination collection does not support adding items.
+ public static TCollection Add(this TCollection destination, IEnumerable source)
+ where TCollection : ICollection
{
- if (destination == null)
- {
- throw new ArgumentNullException(nameof(destination));
- }
-
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
-
+ if (destination == null) throw new ArgumentNullException(nameof(destination));
+ if (source == null) throw new ArgumentNullException(nameof(source));
if (destination is List list)
{
list.AddRange(source);
return destination;
}
- foreach (var item in source)
- {
- destination.Add(item);
- }
-
+ foreach (var item in source) destination.Add(item);
return destination;
}
public static ICollection> All => new List>
{
- Pulsar,
- Kafka,
- Http,
- Websockets,
- Sqs,
- Sns,
- AMQP,
- MQTT,
+ Pulsar, Kafka, Http, Websockets, Sqs, Sns, AMQP, MQTT, AnypointMQ, GooglePubSub,
+ IBMMQ, JMS, MQTT5, NATS, ROS2, Solace,
};
public static ICollection> Http => new List>
{
- new HttpOperationBinding(),
- new HttpMessageBinding(),
+ new HttpOperationBinding(), new HttpMessageBinding(),
};
public static ICollection> Websockets => new List>
@@ -69,42 +63,72 @@ public static TCollection Add(
public static ICollection> Kafka => new List>
{
- new KafkaServerBinding(),
- new KafkaChannelBinding(),
- new KafkaOperationBinding(),
- new KafkaMessageBinding(),
+ new KafkaServerBinding(), new KafkaChannelBinding(), new KafkaOperationBinding(), new KafkaMessageBinding(),
};
public static ICollection> Pulsar => new List>
{
- new PulsarServerBinding(),
- new PulsarChannelBinding(),
+ new PulsarServerBinding(), new PulsarChannelBinding(),
};
public static ICollection> Sqs => new List>
{
- new SqsChannelBinding(),
- new SqsOperationBinding(),
+ new SqsChannelBinding(), new SqsOperationBinding(),
};
public static ICollection> Sns => new List>
{
- new SnsChannelBinding(),
- new SnsOperationBinding(),
+ new SnsChannelBinding(), new SnsOperationBinding(),
};
public static ICollection> AMQP => new List>
{
- new AMQPChannelBinding(),
- new AMQPOperationBinding(),
- new AMQPMessageBinding(),
+ new AMQPChannelBinding(), new AMQPOperationBinding(), new AMQPMessageBinding(),
+ };
+
+ public static ICollection> AnypointMQ => new List>
+ {
+ new AnypointMQChannelBinding(), new AnypointMQMessageBinding(),
+ };
+
+ public static ICollection> GooglePubSub => new List>
+ {
+ new GooglePubSubChannelBinding(), new GooglePubSubMessageBinding(),
+ };
+
+ public static ICollection> IBMMQ => new List>
+ {
+ new IBMMQServerBinding(), new IBMMQChannelBinding(), new IBMMQMessageBinding(),
+ };
+
+ public static ICollection> JMS => new List>
+ {
+ new JMSServerBinding(), new JMSChannelBinding(), new JMSMessageBinding(),
};
public static ICollection> MQTT => new List>
{
- new MQTTServerBinding(),
- new MQTTOperationBinding(),
- new MQTTMessageBinding(),
+ new MQTTServerBinding(), new MQTTOperationBinding(), new MQTTMessageBinding(),
+ };
+
+ public static ICollection> MQTT5 => new List>
+ {
+ new MQTT5ServerBinding(),
+ };
+
+ public static ICollection> NATS => new List>
+ {
+ new NATSOperationBinding(),
+ };
+
+ public static ICollection> ROS2 => new List>
+ {
+ new ROS2ServerBinding(), new ROS2OperationBinding(),
+ };
+
+ public static ICollection> Solace => new List>
+ {
+ new SolaceServerBinding(), new SolaceOperationBinding(),
};
}
}
diff --git a/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubChannelBinding.cs b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubChannelBinding.cs
new file mode 100644
index 0000000..d24a8ea
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubChannelBinding.cs
@@ -0,0 +1,68 @@
+namespace ByteBard.AsyncAPI.Bindings.GooglePubSub
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class GooglePubSubChannelBinding : ChannelBinding
+ {
+ public Dictionary Labels { get; set; }
+ public string MessageRetentionDuration { get; set; }
+ public MessageStoragePolicy MessageStoragePolicy { get; set; }
+ public SchemaSettings SchemaSettings { get; set; }
+
+ public override string BindingKey => "googlepubsub";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "labels", (a, n) => { a.Labels = n.CreateSimpleMap(s => s.GetScalarValue()); } },
+ { "messageRetentionDuration", (a, n) => { a.MessageRetentionDuration = n.GetScalarValue(); } },
+ { "messageStoragePolicy", (a, n) => { a.MessageStoragePolicy = n.ParseMap(messageStoragePolicyFields); } },
+ { "schemaSettings", (a, n) => { a.SchemaSettings = n.ParseMap(schemaSettingsFields); } },
+ };
+
+ private static readonly FixedFieldMap messageStoragePolicyFields = new()
+ {
+ { "allowedPersistenceRegions", (a, n) => { a.AllowedPersistenceRegions = n.CreateSimpleList(s => s.GetScalarValue()); } },
+ };
+
+ private static readonly FixedFieldMap schemaSettingsFields = new()
+ {
+ { "encoding", (a, n) => { a.Encoding = n.GetScalarValue(); } },
+ { "firstRevisionId", (a, n) => { a.FirstRevisionId = n.GetScalarValue(); } },
+ { "lastRevisionId", (a, n) => { a.LastRevisionId = n.GetScalarValue(); } },
+ { "name", (a, n) => { a.Name = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the Google Pub/Sub channel binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the Google Pub/Sub channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the Google Pub/Sub channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalMap("labels", this.Labels, (w, v) => w.WriteValue(v));
+ writer.WriteOptionalProperty("messageRetentionDuration", this.MessageRetentionDuration);
+ writer.WriteOptionalObject("messageStoragePolicy", this.MessageStoragePolicy, (w, v) => v.Serialize(w));
+ writer.WriteOptionalObject("schemaSettings", this.SchemaSettings, (w, v) => v.Serialize(w));
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubMessageBinding.cs b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubMessageBinding.cs
new file mode 100644
index 0000000..b934ace
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/GooglePubSubMessageBinding.cs
@@ -0,0 +1,57 @@
+namespace ByteBard.AsyncAPI.Bindings.GooglePubSub
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class GooglePubSubMessageBinding : MessageBinding
+ {
+ public Dictionary Attributes { get; set; }
+ public string OrderingKey { get; set; }
+ public MessageSchema Schema { get; set; }
+
+ public override string BindingKey => "googlepubsub";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "attributes", (a, n) => { a.Attributes = n.CreateSimpleMap(s => s.GetScalarValue()); } },
+ { "orderingKey", (a, n) => { a.OrderingKey = n.GetScalarValue(); } },
+ { "schema", (a, n) => { a.Schema = n.ParseMap(schemaFields); } },
+ };
+
+ private static readonly FixedFieldMap schemaFields = new()
+ {
+ { "name", (a, n) => { a.Name = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the Google Pub/Sub message binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the Google Pub/Sub message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the Google Pub/Sub message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalMap("attributes", this.Attributes, (w, v) => w.WriteValue(v));
+ writer.WriteOptionalProperty("orderingKey", this.OrderingKey);
+ writer.WriteOptionalObject("schema", this.Schema, (w, v) => v.Serialize(w));
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageSchema.cs b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageSchema.cs
new file mode 100644
index 0000000..a31a0bd
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageSchema.cs
@@ -0,0 +1,21 @@
+namespace ByteBard.AsyncAPI.Bindings.GooglePubSub
+{
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class MessageSchema : IAsyncApiElement
+ {
+ public string Name { get; set; }
+
+ ///
+ /// Writes the message schema object, omitting an unset name.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("name", this.Name);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageStoragePolicy.cs b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageStoragePolicy.cs
new file mode 100644
index 0000000..c5f8d42
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/MessageStoragePolicy.cs
@@ -0,0 +1,22 @@
+namespace ByteBard.AsyncAPI.Bindings.GooglePubSub
+{
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class MessageStoragePolicy : IAsyncApiElement
+ {
+ public IEnumerable AllowedPersistenceRegions { get; set; }
+
+ ///
+ /// Writes the message storage policy object, omitting null or empty persistence regions.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalCollection("allowedPersistenceRegions", this.AllowedPersistenceRegions, (w, v) => w.WriteValue(v));
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/SchemaSettings.cs b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/SchemaSettings.cs
new file mode 100644
index 0000000..9b6a955
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/GooglePubSub/SchemaSettings.cs
@@ -0,0 +1,27 @@
+namespace ByteBard.AsyncAPI.Bindings.GooglePubSub
+{
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class SchemaSettings : IAsyncApiElement
+ {
+ public string Encoding { get; set; }
+ public string FirstRevisionId { get; set; }
+ public string LastRevisionId { get; set; }
+ public string Name { get; set; }
+
+ ///
+ /// Writes the schema settings object, omitting unset values.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("encoding", this.Encoding);
+ writer.WriteOptionalProperty("firstRevisionId", this.FirstRevisionId);
+ writer.WriteOptionalProperty("lastRevisionId", this.LastRevisionId);
+ writer.WriteOptionalProperty("name", this.Name);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQChannelBinding.cs b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQChannelBinding.cs
new file mode 100644
index 0000000..8608aee
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQChannelBinding.cs
@@ -0,0 +1,69 @@
+namespace ByteBard.AsyncAPI.Bindings.IBMMQ
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class IBMMQChannelBinding : ChannelBinding
+ {
+ public string DestinationType { get; set; }
+ public Queue Queue { get; set; }
+ public Topic Topic { get; set; }
+ public int? MaxMsgLength { get; set; }
+
+ public override string BindingKey => "ibmmq";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "destinationType", (a, n) => { a.DestinationType = n.GetScalarValue(); } },
+ { "queue", (a, n) => { a.Queue = n.ParseMap(queueFields); } },
+ { "topic", (a, n) => { a.Topic = n.ParseMap(topicFields); } },
+ { "maxMsgLength", (a, n) => { a.MaxMsgLength = n.GetIntegerValue(); } },
+ };
+
+ private static readonly FixedFieldMap queueFields = new()
+ {
+ { "objectName", (a, n) => { a.ObjectName = n.GetScalarValue(); } },
+ { "isPartitioned", (a, n) => { a.IsPartitioned = n.GetBooleanValue(); } },
+ { "exclusive", (a, n) => { a.Exclusive = n.GetBooleanValue(); } },
+ };
+
+ private static readonly FixedFieldMap topicFields = new()
+ {
+ { "string", (a, n) => { a.StringValue = n.GetScalarValue(); } },
+ { "objectName", (a, n) => { a.ObjectName = n.GetScalarValue(); } },
+ { "durablePermitted", (a, n) => { a.DurablePermitted = n.GetBooleanValue(); } },
+ { "lastMsgRetained", (a, n) => { a.LastMsgRetained = n.GetBooleanValue(); } },
+ };
+
+ ///
+ /// Writes the IBM MQ channel binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the IBM MQ channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the IBM MQ channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("destinationType", this.DestinationType);
+ writer.WriteOptionalObject("queue", this.Queue, (w, v) => v.Serialize(w));
+ writer.WriteOptionalObject("topic", this.Topic, (w, v) => v.Serialize(w));
+ writer.WriteOptionalProperty("maxMsgLength", this.MaxMsgLength);
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQMessageBinding.cs b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQMessageBinding.cs
new file mode 100644
index 0000000..d6e7bb7
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQMessageBinding.cs
@@ -0,0 +1,54 @@
+namespace ByteBard.AsyncAPI.Bindings.IBMMQ
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class IBMMQMessageBinding : MessageBinding
+ {
+ public string Type { get; set; }
+ public string Headers { get; set; }
+ public string Description { get; set; }
+ public long? Expiry { get; set; }
+
+ public override string BindingKey => "ibmmq";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "type", (a, n) => { a.Type = n.GetScalarValue(); } },
+ { "headers", (a, n) => { a.Headers = n.GetScalarValue(); } },
+ { "description", (a, n) => { a.Description = n.GetScalarValue(); } },
+ { "expiry", (a, n) => { a.Expiry = n.GetLongValue(); } },
+ };
+
+ ///
+ /// Writes the IBM MQ message binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the IBM MQ message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the IBM MQ message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteOptionalProperty("headers", this.Headers);
+ writer.WriteOptionalProperty("description", this.Description);
+ writer.WriteOptionalProperty("expiry", this.Expiry);
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQServerBinding.cs
new file mode 100644
index 0000000..9cfd800
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/IBMMQServerBinding.cs
@@ -0,0 +1,57 @@
+namespace ByteBard.AsyncAPI.Bindings.IBMMQ
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class IBMMQServerBinding : ServerBinding
+ {
+ public string GroupId { get; set; }
+ public string CcdtQueueManagerName { get; set; }
+ public string CipherSpec { get; set; }
+ public bool? MultiEndpointServer { get; set; }
+ public int? HeartBeatInterval { get; set; }
+
+ public override string BindingKey => "ibmmq";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "groupId", (a, n) => { a.GroupId = n.GetScalarValue(); } },
+ { "ccdtQueueManagerName", (a, n) => { a.CcdtQueueManagerName = n.GetScalarValue(); } },
+ { "cipherSpec", (a, n) => { a.CipherSpec = n.GetScalarValue(); } },
+ { "multiEndpointServer", (a, n) => { a.MultiEndpointServer = n.GetBooleanValue(); } },
+ { "heartBeatInterval", (a, n) => { a.HeartBeatInterval = n.GetIntegerValue(); } },
+ };
+
+ ///
+ /// Writes the IBM MQ server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the IBM MQ server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the IBM MQ server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("groupId", this.GroupId);
+ writer.WriteOptionalProperty("ccdtQueueManagerName", this.CcdtQueueManagerName);
+ writer.WriteOptionalProperty("cipherSpec", this.CipherSpec);
+ writer.WriteOptionalProperty("multiEndpointServer", this.MultiEndpointServer);
+ writer.WriteOptionalProperty("heartBeatInterval", this.HeartBeatInterval);
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Queue.cs b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Queue.cs
new file mode 100644
index 0000000..583d5cc
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Queue.cs
@@ -0,0 +1,25 @@
+namespace ByteBard.AsyncAPI.Bindings.IBMMQ
+{
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class Queue : IAsyncApiElement
+ {
+ public string ObjectName { get; set; }
+ public bool? IsPartitioned { get; set; }
+ public bool? Exclusive { get; set; }
+
+ ///
+ /// Writes the IBM MQ queue settings object, omitting unset values.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("objectName", this.ObjectName);
+ writer.WriteOptionalProperty("isPartitioned", this.IsPartitioned);
+ writer.WriteOptionalProperty("exclusive", this.Exclusive);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Topic.cs b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Topic.cs
new file mode 100644
index 0000000..2bac7ef
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/IBMMQ/Topic.cs
@@ -0,0 +1,27 @@
+namespace ByteBard.AsyncAPI.Bindings.IBMMQ
+{
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ public class Topic : IAsyncApiElement
+ {
+ public string StringValue { get; set; }
+ public string ObjectName { get; set; }
+ public bool? DurablePermitted { get; set; }
+ public bool? LastMsgRetained { get; set; }
+
+ ///
+ /// Writes the IBM MQ topic settings object, omitting unset values.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("string", this.StringValue);
+ writer.WriteOptionalProperty("objectName", this.ObjectName);
+ writer.WriteOptionalProperty("durablePermitted", this.DurablePermitted);
+ writer.WriteOptionalProperty("lastMsgRetained", this.LastMsgRetained);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/JMS/JMSChannelBinding.cs b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSChannelBinding.cs
new file mode 100644
index 0000000..2a53bca
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSChannelBinding.cs
@@ -0,0 +1,52 @@
+namespace ByteBard.AsyncAPI.Bindings.JMS
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding for a JMS destination channel.
+ public class JMSChannelBinding : ChannelBinding
+ {
+ public string Destination { get; set; }
+
+ public string DestinationType { get; set; }
+
+ public override string BindingKey => "jms";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "destination", (a, n) => { a.Destination = n.GetScalarValue(); } },
+ { "destinationType", (a, n) => { a.DestinationType = n.GetScalarValue(); } },
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the JMS channel binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+
+ ///
+ /// Writes the JMS channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the JMS channel binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("destination", this.Destination);
+ writer.WriteOptionalProperty("destinationType", this.DestinationType);
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/JMS/JMSMessageBinding.cs b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSMessageBinding.cs
new file mode 100644
index 0000000..fd307ec
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSMessageBinding.cs
@@ -0,0 +1,58 @@
+namespace ByteBard.AsyncAPI.Bindings.JMS
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding for a JMS message.
+ public class JMSMessageBinding : MessageBinding
+ {
+ /// Schema describing the JMS message headers.
+ public AsyncApiJsonSchema Headers { get; set; }
+
+ public override string BindingKey => "jms";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "headers", (a, n) => { a.Headers = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ };
+
+ ///
+ /// Writes the JMS message binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalObject("headers", this.Headers, (w, h) => h.SerializeV2(w));
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+
+ ///
+ /// Writes the JMS message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the JMS message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalObject("headers", this.Headers, (w, h) => h.SerializeV3(w));
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/JMS/JMSProperty.cs b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSProperty.cs
new file mode 100644
index 0000000..5709695
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSProperty.cs
@@ -0,0 +1,26 @@
+namespace ByteBard.AsyncAPI.Bindings.JMS
+{
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// A name and typed value for a JMS connection property.
+ public class JMSProperty : IAsyncApiElement
+ {
+ public string Name { get; set; }
+
+ public AsyncApiAny Value { get; set; }
+
+ ///
+ /// Writes the connection property object, preserving the JSON type of its value and omitting unset fields.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("name", this.Name);
+ writer.WriteOptionalObject("value", this.Value, (w, v) => w.WriteAny(v));
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/JMS/JMSServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSServerBinding.cs
new file mode 100644
index 0000000..a9efc18
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/JMS/JMSServerBinding.cs
@@ -0,0 +1,63 @@
+namespace ByteBard.AsyncAPI.Bindings.JMS
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding for a JMS server connection.
+ public class JMSServerBinding : ServerBinding
+ {
+ public string JmsConnectionFactory { get; set; }
+
+ public IList Properties { get; set; }
+
+ public string ClientID { get; set; }
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "jmsConnectionFactory", (a, n) => { a.JmsConnectionFactory = n.GetScalarValue(); } },
+ { "properties", (a, n) => { a.Properties = n.CreateList(node => node.ParseMap(JMSPropertyFixedFields)); } },
+ { "clientID", (a, n) => { a.ClientID = n.GetScalarValue(); } },
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ };
+
+ private static readonly FixedFieldMap JMSPropertyFixedFields = new()
+ {
+ { "name", (a, n) => { a.Name = n.GetScalarValue(); } },
+ { "value", (a, n) => { a.Value = n.CreateAny(); } },
+ };
+
+ public override string BindingKey => "jms";
+
+ ///
+ /// Writes the JMS server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+
+ ///
+ /// Writes the JMS server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the JMS server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// The connection factory is written even when null; an unset binding version and empty properties collection are omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteRequiredProperty("jmsConnectionFactory", this.JmsConnectionFactory);
+ writer.WriteOptionalCollection("properties", this.Properties, (w, p) => p.Serialize(w));
+ writer.WriteOptionalProperty("clientID", this.ClientID);
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaChannelBinding.cs b/src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaChannelBinding.cs
index bb96af5..7fed53b 100644
--- a/src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaChannelBinding.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaChannelBinding.cs
@@ -1,6 +1,7 @@
namespace ByteBard.AsyncAPI.Bindings.Kafka
{
using System;
+ using System.Collections.Generic;
using ByteBard.AsyncAPI.Models;
using ByteBard.AsyncAPI.Readers.ParseNodes;
using ByteBard.AsyncAPI.Writers;
@@ -37,7 +38,7 @@ public class KafkaChannelBinding : ChannelBinding
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
{ "topic", (a, n) => { a.Topic = n.GetScalarValue(); } },
{ "partitions", (a, n) => { a.Partitions = n.GetIntegerValue(); } },
- { "topicConfiguration", (a, n) => { a.TopicConfiguration = n.ParseMap(kafkaChannelTopicConfigurationObjectFixedFields); } },
+ { "topicConfiguration", (a, n) => { a.TopicConfiguration = ParseTopicConfiguration(n); } },
{ "replicas", (a, n) => { a.Replicas = n.GetIntegerValue(); } },
};
@@ -54,6 +55,33 @@ public class KafkaChannelBinding : ChannelBinding
{ "confluent.value.subject.name.strategy", (a, n) => { a.ConfluentValueSubjectName = n.GetScalarValue(); } },
};
+ ///
+ /// Parses known topic settings and retains other settings as JSON values in AdditionalProperties.
+ ///
+ /// A new topic configuration; known fields with parsing errors may remain unset.
+ /// Reader and AsyncAPI exceptions from known field handlers are recorded as diagnostics.
+ /// is not a map.
+ private static TopicConfigurationObject ParseTopicConfiguration(ParseNode node)
+ {
+ var mapNode = node.CheckMapNode(node.Context.GetLocation());
+ var configuration = new TopicConfigurationObject();
+ var emptyPatternFields = new Dictionary, Action>();
+
+ foreach (var propertyNode in mapNode)
+ {
+ if (kafkaChannelTopicConfigurationObjectFixedFields.ContainsKey(propertyNode.Name))
+ {
+ propertyNode.ParseField(configuration, kafkaChannelTopicConfigurationObjectFixedFields, emptyPatternFields);
+ }
+ else
+ {
+ configuration.AdditionalProperties[propertyNode.Name] = propertyNode.Value.CreateAny();
+ }
+ }
+
+ return configuration;
+ }
+
public override void SerializeV2(IAsyncApiWriter writer)
{
this.SerializeV3(writer);
diff --git a/src/ByteBard.AsyncAPI.Bindings/Kafka/TopicConfigurationObject.cs b/src/ByteBard.AsyncAPI.Bindings/Kafka/TopicConfigurationObject.cs
index 860db80..6b8a196 100644
--- a/src/ByteBard.AsyncAPI.Bindings/Kafka/TopicConfigurationObject.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/Kafka/TopicConfigurationObject.cs
@@ -8,6 +8,19 @@
public class TopicConfigurationObject : IAsyncApiElement
{
+ private static readonly HashSet FixedFieldNames = new()
+ {
+ AsyncApiConstants.CleanupPolicy,
+ AsyncApiConstants.RetentionMilliseconds,
+ AsyncApiConstants.RetentionBytes,
+ AsyncApiConstants.DeleteRetentionMilliseconds,
+ AsyncApiConstants.MaxMessageBytes,
+ AsyncApiConstants.ConfluentKeySchemaValidation,
+ AsyncApiConstants.ConfluentKeySubjectName,
+ AsyncApiConstants.ConfluentValueSchemaValidation,
+ AsyncApiConstants.ConfluentValueSubjectName,
+ };
+
///
/// The cleanup.policy configuration option.
///
@@ -53,6 +66,17 @@ public class TopicConfigurationObject : IAsyncApiElement
///
public string ConfluentValueSubjectName { get; set; }
+ ///
+ /// Additional Kafka topic configuration values not represented by fixed properties.
+ /// Values are retained as JSON-backed instances.
+ ///
+ public Dictionary AdditionalProperties { get; set; } = new();
+
+ ///
+ /// Writes the topic configuration object, including additional settings whose names do not match fixed fields.
+ ///
+ /// Additional settings with fixed field names are skipped even when the corresponding fixed value is unset.
+ /// is null.
public void Serialize(IAsyncApiWriter writer)
{
if (writer is null)
@@ -70,7 +94,19 @@ public void Serialize(IAsyncApiWriter writer)
writer.WriteOptionalProperty(AsyncApiConstants.ConfluentKeySubjectName, this.ConfluentKeySubjectName);
writer.WriteOptionalProperty(AsyncApiConstants.ConfluentValueSchemaValidation, this.ConfluentValueSchemaValidation);
writer.WriteOptionalProperty(AsyncApiConstants.ConfluentValueSubjectName, this.ConfluentValueSubjectName);
+ if (this.AdditionalProperties != null)
+ {
+ foreach (var property in this.AdditionalProperties)
+ {
+ if (!FixedFieldNames.Contains(property.Key))
+ {
+ writer.WritePropertyName(property.Key);
+ writer.WriteAny(property.Value);
+ }
+ }
+ }
+
writer.WriteEndObject();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
index 44cec88..93214f6 100644
--- a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
@@ -31,19 +31,50 @@ public class MQTTMessageBinding : MessageBinding
///
/// The topic (channel URI) for a response message.
///
- public string ResponseTopic { get; set; }
+ public string ResponseTopic
+ {
+ get => this.ResponseTopicValue?.Value;
+ set => this.ResponseTopicValue = value == null ? null : MQTTStringOrSchema.FromString(value);
+ }
+ /// The string-or-schema response topic value.
+ public MQTTStringOrSchema ResponseTopicValue { get; set; }
+
+ ///
+ /// Writes the MQTT message binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
public override void SerializeV2(IAsyncApiWriter writer)
{
- this.SerializeV3(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi2_0);
}
+ ///
+ /// Writes the MQTT message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
public override void SerializeV3(IAsyncApiWriter writer)
{
- this.SerializeProperties(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
}
+ ///
+ /// Writes the MQTT message binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// ResponseTopicValue is written as a string or schema; nested schemas use AsyncAPI 3.0 serialization.
+ /// is null.
public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
+ }
+
+ ///
+ /// Writes the MQTT message binding object and extensions, using the selected version for nested schemas.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// is null.
+ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version)
{
if (writer is null)
{
@@ -52,9 +83,10 @@ public override void SerializeProperties(IAsyncApiWriter writer)
writer.WriteStartObject();
writer.WriteOptionalProperty("payloadFormatIndicator", this.PayloadFormatIndicator);
- writer.WriteOptionalObject("correlationData", this.CorrelationData, (w, h) => h.SerializeV2(w));
+ writer.WriteOptionalObject("correlationData", this.CorrelationData, (w, h) => { if (version == AsyncApiVersion.AsyncApi2_0) h.SerializeV2(w); else h.SerializeV3(w); });
writer.WriteOptionalProperty("contentType", this.ContentType);
- writer.WriteOptionalProperty("responseTopic", this.ResponseTopic);
+ if (this.ResponseTopicValue?.Value != null) writer.WriteOptionalProperty("responseTopic", this.ResponseTopicValue.Value);
+ else if (this.ResponseTopicValue?.Schema != null) writer.WriteOptionalObject("responseTopic", this.ResponseTopicValue.Schema, (w, schema) => { if (version == AsyncApiVersion.AsyncApi2_0) schema.SerializeV2(w); else schema.SerializeV3(w); });
writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
writer.WriteExtensions(this.Extensions);
writer.WriteEndObject();
@@ -64,10 +96,11 @@ public override void SerializeProperties(IAsyncApiWriter writer)
protected override FixedFieldMap FixedFieldMap => new()
{
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
{ "payloadFormatIndicator", (a, n) => { a.PayloadFormatIndicator = n.GetIntegerValueOrDefault(); } },
{ "correlationData", (a, n) => { a.CorrelationData = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
{ "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } },
- { "responseTopic", (a, n) => { a.ResponseTopic = n.GetScalarValue(); } },
+ { "responseTopic", (a, n) => { a.ResponseTopicValue = MQTTStringOrSchema.Parse(n); } },
};
}
}
diff --git a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
index fb593aa..193f4e6 100644
--- a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
@@ -1,6 +1,7 @@
namespace ByteBard.AsyncAPI.Bindings.MQTT
{
using System;
+ using ByteBard.AsyncAPI.Models;
using ByteBard.AsyncAPI.Readers.ParseNodes;
using ByteBard.AsyncAPI.Writers;
@@ -9,42 +10,85 @@
///
public class MQTTOperationBinding : OperationBinding
{
+ private int? qos;
+ private bool? retain;
+
///
/// Defines the Quality of Service (QoS) levels for the message flow between client and server.
/// Its value MUST be either 0 (At most once delivery), 1 (At least once delivery), or 2 (Exactly once delivery).
///
- public int QoS { get; set; }
+ public int QoS
+ {
+ get => this.qos ?? 0;
+ set => this.qos = value;
+ }
///
/// Whether the broker should retain the message or not.
///
- public bool Retain { get; set; }
+ public bool Retain
+ {
+ get => this.retain ?? false;
+ set => this.retain = value;
+ }
///
/// Interval in seconds or a Schema Object containing the definition of the lifetime of the message.
///
- public int? MessageExpiryInterval { get; set; }
+ public int? MessageExpiryInterval
+ {
+ get => this.MessageExpiryIntervalValue?.Integer;
+ set => this.MessageExpiryIntervalValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
+ }
+
+ /// The integer-or-schema message expiry interval value.
+ public MQTTIntegerOrSchema MessageExpiryIntervalValue { get; set; }
public override string BindingKey => "mqtt";
protected override FixedFieldMap FixedFieldMap => new()
{
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
{ "qos", (a, n) => { a.QoS = n.GetIntegerValue(); } },
{ "retain", (a, n) => { a.Retain = n.GetBooleanValue(); } },
- { "messageExpiryInterval", (a, n) => { a.MessageExpiryInterval = n.GetIntegerValueOrDefault(); } },
+ { "messageExpiryInterval", (a, n) => { a.MessageExpiryIntervalValue = MQTTIntegerOrSchema.Parse(n); } },
};
+ ///
+ /// Writes the MQTT operation binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
public override void SerializeV2(IAsyncApiWriter writer)
{
- this.SerializeV3(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi2_0);
}
+ ///
+ /// Writes the MQTT operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
public override void SerializeV3(IAsyncApiWriter writer)
{
- this.SerializeProperties(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
}
+ ///
+ /// Writes the MQTT operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// Unset QoS and retain values are omitted. Message expiry is written as an integer or schema.
+ /// is null.
public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
+ }
+
+ ///
+ /// Writes the MQTT operation binding object and extensions, using the selected version for nested schemas.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// is null.
+ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version)
{
if (writer is null)
{
@@ -52,9 +96,14 @@ public override void SerializeProperties(IAsyncApiWriter writer)
}
writer.WriteStartObject();
- writer.WriteRequiredProperty("qos", this.QoS);
- writer.WriteRequiredProperty("retain", this.Retain);
- writer.WriteOptionalProperty("messageExpiryInterval", this.MessageExpiryInterval);
+ writer.WriteOptionalProperty("qos", this.qos);
+ writer.WriteOptionalProperty("retain", this.retain);
+ if (this.MessageExpiryIntervalValue?.LongInteger is long integer) writer.WriteRequiredProperty("messageExpiryInterval", integer);
+ else if (this.MessageExpiryIntervalValue?.Schema != null) writer.WriteOptionalObject("messageExpiryInterval", this.MessageExpiryIntervalValue.Schema, (w, schema) =>
+ {
+ if (version == AsyncApiVersion.AsyncApi2_0) schema.SerializeV2(w);
+ else schema.SerializeV3(w);
+ });
writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
writer.WriteExtensions(this.Extensions);
writer.WriteEndObject();
diff --git a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTServerBinding.cs
index a137f8b..0876fa9 100644
--- a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTServerBinding.cs
+++ b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTServerBinding.cs
@@ -37,13 +37,27 @@ public class MQTTServerBinding : ServerBinding
/// Interval in seconds the broker maintains a session
/// for a disconnected client until this interval expires.
///
- public int? SessionExpiryInterval { get; set; }
+ public int? SessionExpiryInterval
+ {
+ get => this.SessionExpiryIntervalValue?.Integer;
+ set => this.SessionExpiryIntervalValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
+ }
+
+ /// The integer-or-schema session expiry interval value.
+ public MQTTIntegerOrSchema SessionExpiryIntervalValue { get; set; }
///
/// Number of bytes representing the maximum packet size
/// the client is willing to accept.
///
- public int? MaximumPacketSize { get; set; }
+ public int? MaximumPacketSize
+ {
+ get => this.MaximumPacketSizeValue?.Integer;
+ set => this.MaximumPacketSizeValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
+ }
+
+ /// The integer-or-schema maximum packet size value.
+ public MQTTIntegerOrSchema MaximumPacketSizeValue { get; set; }
public override string BindingKey => "mqtt";
@@ -54,8 +68,8 @@ public class MQTTServerBinding : ServerBinding
{ "cleanSession", (a, n) => { a.CleanSession = n.GetBooleanValueOrDefault(); } },
{ "lastWill", (a, n) => { a.LastWill = n.ParseMap(LastWillFixedFields); } },
{ "keepAlive", (a, n) => { a.KeepAlive = n.GetIntegerValueOrDefault(); } },
- { "sessionExpiryInterval", (a, n) => { a.SessionExpiryInterval = n.GetIntegerValueOrDefault(); } },
- { "maximumPacketSize", (a, n) => { a.MaximumPacketSize = n.GetIntegerValueOrDefault(); } },
+ { "sessionExpiryInterval", (a, n) => { a.SessionExpiryIntervalValue = MQTTIntegerOrSchema.Parse(n); } },
+ { "maximumPacketSize", (a, n) => { a.MaximumPacketSizeValue = MQTTIntegerOrSchema.Parse(n); } },
};
private static FixedFieldMap LastWillFixedFields = new()
@@ -66,17 +80,41 @@ public class MQTTServerBinding : ServerBinding
{ "retain", (a, n) => { a.Retain = n.GetBooleanValue(); } },
};
+ ///
+ /// Writes the MQTT server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
public override void SerializeV2(IAsyncApiWriter writer)
{
- this.SerializeV3(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi2_0);
}
+ ///
+ /// Writes the MQTT server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
public override void SerializeV3(IAsyncApiWriter writer)
{
- this.SerializeProperties(writer);
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
}
+ ///
+ /// Writes the MQTT server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset client ID is omitted. Session expiry and maximum packet size are written as integers or schemas.
+ /// is null.
public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
+ }
+
+ ///
+ /// Writes the MQTT server binding object and extensions, using the selected version for nested schemas.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// is null.
+ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version)
{
if (writer is null)
{
@@ -84,15 +122,33 @@ public override void SerializeProperties(IAsyncApiWriter writer)
}
writer.WriteStartObject();
- writer.WriteRequiredProperty("clientId", this.ClientId);
+ writer.WriteOptionalProperty("clientId", this.ClientId);
writer.WriteOptionalProperty("cleanSession", this.CleanSession);
writer.WriteOptionalObject("lastWill", this.LastWill, (w, l) => l.Serialize(w));
writer.WriteOptionalProperty("keepAlive", this.KeepAlive);
- writer.WriteOptionalProperty("sessionExpiryInterval", this.SessionExpiryInterval);
- writer.WriteOptionalProperty("maximumPacketSize", this.MaximumPacketSize);
+ this.WriteIntegerOrSchema(writer, "sessionExpiryInterval", this.SessionExpiryIntervalValue, version);
+ this.WriteIntegerOrSchema(writer, "maximumPacketSize", this.MaximumPacketSizeValue, version);
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
writer.WriteExtensions(this.Extensions);
writer.WriteEndObject();
}
+
+ ///
+ /// Writes a named integer or schema property, omitting it when the value is null.
+ ///
+ /// The writer receiving the value.
+ /// The property name to write.
+ /// The integer or schema to write, or null to omit the property.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// A value is present and or is null.
+ private void WriteIntegerOrSchema(IAsyncApiWriter writer, string name, MQTTIntegerOrSchema value, AsyncApiVersion version)
+ {
+ if (value?.LongInteger is long integer) writer.WriteRequiredProperty(name, integer);
+ else if (value?.Schema != null) writer.WriteOptionalObject(name, value.Schema, (w, schema) =>
+ {
+ if (version == AsyncApiVersion.AsyncApi2_0) schema.SerializeV2(w);
+ else schema.SerializeV3(w);
+ });
+ }
}
}
diff --git a/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTValueUnions.cs b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTValueUnions.cs
new file mode 100644
index 0000000..71f809b
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTValueUnions.cs
@@ -0,0 +1,145 @@
+namespace ByteBard.AsyncAPI.Bindings.MQTT
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// A MQTT binding value represented by either an integer or a JSON Schema.
+ public sealed class MQTTIntegerOrSchema
+ {
+ ///
+ /// Stores the supplied integer and schema without validating that exactly one is present.
+ ///
+ private MQTTIntegerOrSchema(long? integer, AsyncApiJsonSchema schema)
+ {
+ this.LongInteger = integer;
+ this.Schema = schema;
+ }
+
+ /// The integer value when it is within the range of the legacy int API.
+ public int? Integer => this.LongInteger.HasValue && this.LongInteger.Value >= int.MinValue && this.LongInteger.Value <= int.MaxValue
+ ? (int)this.LongInteger.Value
+ : null;
+
+ /// The full-width integer value.
+ public long? LongInteger { get; }
+
+ public AsyncApiJsonSchema Schema { get; }
+
+ ///
+ /// Creates an integer alternative without enforcing MQTT field limits.
+ ///
+ public static MQTTIntegerOrSchema FromInteger(int value) => new(value, null);
+
+ ///
+ /// Creates an integer alternative without enforcing MQTT field limits.
+ ///
+ /// Values outside the Int32 range remain available through LongInteger; Integer returns null for them.
+ public static MQTTIntegerOrSchema FromLong(long value) => new(value, null);
+
+ ///
+ /// Creates a schema alternative, retaining the supplied schema or reference without copying it.
+ ///
+ /// is null.
+ public static MQTTIntegerOrSchema FromSchema(AsyncApiJsonSchema schema) => new(null, schema ?? throw new ArgumentNullException(nameof(schema)));
+
+ ///
+ /// Parses a scalar as a signed 64-bit integer, or an object as a schema or schema reference.
+ ///
+ /// A value containing the parsed scalar or schema.
+ /// Schema field errors handled by the reader are recorded as diagnostics.
+ /// The scalar cannot be parsed as an Int64, or a non-scalar node is not a schema object.
+ /// A schema reference has a non-scalar $ref value.
+ public static MQTTIntegerOrSchema Parse(ParseNode node)
+ {
+ return node is ValueNode
+ ? FromLong(node.GetLongValue())
+ : FromSchema(AsyncApiJsonSchemaDeserializer.LoadSchema(node));
+ }
+
+ ///
+ /// Writes the scalar value or schema directly, without a property name or wrapper object.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// The writer is null when writing a scalar or an ordinary schema.
+ /// The writer is null when writing a schema reference.
+ public void Serialize(IAsyncApiWriter writer, AsyncApiVersion version)
+ {
+ if (this.LongInteger.HasValue)
+ {
+ writer.WriteValue(this.LongInteger.Value);
+ }
+ else if (this.Schema != null)
+ {
+ if (version == AsyncApiVersion.AsyncApi2_0) this.Schema.SerializeV2(writer);
+ else this.Schema.SerializeV3(writer);
+ }
+ }
+ }
+
+ /// A MQTT message response topic represented by either a string or a JSON Schema.
+ public sealed class MQTTStringOrSchema
+ {
+ ///
+ /// Stores the supplied response topic and schema without validating that exactly one is present.
+ ///
+ private MQTTStringOrSchema(string value, AsyncApiJsonSchema schema)
+ {
+ this.Value = value;
+ this.Schema = schema;
+ }
+
+ public string Value { get; }
+
+ public AsyncApiJsonSchema Schema { get; }
+
+ ///
+ /// Creates a response topic alternative without validating topic syntax.
+ ///
+ /// is null.
+ public static MQTTStringOrSchema FromString(string value) => new(value ?? throw new ArgumentNullException(nameof(value)), null);
+
+ ///
+ /// Creates a schema alternative, retaining the supplied schema or reference without copying it.
+ ///
+ /// is null.
+ public static MQTTStringOrSchema FromSchema(AsyncApiJsonSchema schema) => new(null, schema ?? throw new ArgumentNullException(nameof(schema)));
+
+ ///
+ /// Converts a scalar to a response topic string, or parses an object as a schema or schema reference.
+ ///
+ /// A value containing the parsed scalar or schema.
+ /// Schema field errors handled by the reader are recorded as diagnostics.
+ /// A non-scalar node is not a schema object.
+ /// A schema reference has a non-scalar $ref value.
+ public static MQTTStringOrSchema Parse(ParseNode node)
+ {
+ return node is ValueNode
+ ? FromString(node.GetScalarValue())
+ : FromSchema(AsyncApiJsonSchemaDeserializer.LoadSchema(node));
+ }
+
+ ///
+ /// Writes the scalar value or schema directly, without a property name or wrapper object.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// The writer is null when writing a scalar or an ordinary schema.
+ /// The writer is null when writing a schema reference.
+ public void Serialize(IAsyncApiWriter writer, AsyncApiVersion version)
+ {
+ if (this.Schema != null)
+ {
+ if (version == AsyncApiVersion.AsyncApi2_0) this.Schema.SerializeV2(writer);
+ else this.Schema.SerializeV3(writer);
+ }
+ else if (this.Value != null)
+ {
+ writer.WriteValue(this.Value);
+ }
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/MQTT5/MQTT5ServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/MQTT5/MQTT5ServerBinding.cs
new file mode 100644
index 0000000..8d2688b
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/MQTT5/MQTT5ServerBinding.cs
@@ -0,0 +1,57 @@
+namespace ByteBard.AsyncAPI.Bindings.MQTT5
+{
+ using System;
+ using ByteBard.AsyncAPI.Bindings.MQTT;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for MQTT 5 server settings. This binding is deprecated.
+ [Obsolete("The MQTT 5 server binding is deprecated.")]
+ public class MQTT5ServerBinding : ServerBinding
+ {
+ /// Session expiration interval in seconds, or a schema describing the value.
+ public MQTTIntegerOrSchema SessionExpiryInterval { get; set; }
+
+ public override string BindingKey => "mqtt5";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
+ { "sessionExpiryInterval", (a, n) => { a.SessionExpiryInterval = MQTTIntegerOrSchema.Parse(n); } },
+ };
+
+ ///
+ /// Writes the MQTT 5 server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeProperties(writer, AsyncApiVersion.AsyncApi2_0);
+ ///
+ /// Writes the MQTT 5 server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
+
+ public override void SerializeProperties(IAsyncApiWriter writer) => this.SerializeProperties(writer, AsyncApiVersion.AsyncApi3_0);
+
+ ///
+ /// Writes the MQTT 5 server binding object and extensions, using the selected version for nested schemas.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 2.0 schema serialization for ; otherwise uses AsyncAPI 3.0.
+ /// is null.
+ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ if (this.SessionExpiryInterval != null)
+ {
+ writer.WritePropertyName("sessionExpiryInterval");
+ this.SessionExpiryInterval.Serialize(writer, version);
+ }
+ writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/NATS/NATSOperationBinding.cs b/src/ByteBard.AsyncAPI.Bindings/NATS/NATSOperationBinding.cs
new file mode 100644
index 0000000..0f59ff0
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/NATS/NATSOperationBinding.cs
@@ -0,0 +1,47 @@
+namespace ByteBard.AsyncAPI.Bindings.NATS
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for NATS operations.
+ public class NATSOperationBinding : OperationBinding
+ {
+ /// Queue group name.
+ public string Queue { get; set; }
+
+ public override string BindingKey => "nats";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "queue", (a, n) => a.Queue = n.GetScalarValue() },
+ { "bindingVersion", (a, n) => a.BindingVersion = n.GetScalarValue() },
+ };
+
+ ///
+ /// Writes the NATS operation binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+ ///
+ /// Writes the NATS operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the NATS operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("queue", this.Queue);
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2OperationBinding.cs b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2OperationBinding.cs
new file mode 100644
index 0000000..8cddc9c
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2OperationBinding.cs
@@ -0,0 +1,50 @@
+namespace ByteBard.AsyncAPI.Bindings.ROS2
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for ROS 2 operation settings.
+ public class ROS2OperationBinding : OperationBinding
+ {
+ public string Role { get; set; }
+ public string Node { get; set; }
+ public ROS2QosPolicies QosPolicies { get; set; }
+
+ public override string BindingKey => "ros2";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "role", (a, n) => { a.Role = n.GetScalarValue(); } },
+ { "node", (a, n) => { a.Node = n.GetScalarValue(); } },
+ { "qosPolicies", (a, n) => { a.QosPolicies = n.ParseMap(ROS2QosPolicies.FixedFields); } },
+ };
+
+ ///
+ /// Writes the ROS 2 operation binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+ ///
+ /// Writes the ROS 2 operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the ROS 2 operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// BindingVersion is not written.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("role", this.Role);
+ writer.WriteOptionalProperty("node", this.Node);
+ writer.WriteOptionalObject("qosPolicies", this.QosPolicies, (w, qos) => qos.Serialize(w));
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2QosPolicies.cs b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2QosPolicies.cs
new file mode 100644
index 0000000..bd33c46
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2QosPolicies.cs
@@ -0,0 +1,47 @@
+namespace ByteBard.AsyncAPI.Bindings.ROS2
+{
+ using System;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Quality-of-service policies for a ROS 2 operation.
+ public class ROS2QosPolicies : IAsyncApiElement
+ {
+ public string Reliability { get; set; }
+ public string History { get; set; }
+ public string Durability { get; set; }
+ public int? Lifespan { get; set; }
+ public int? Deadline { get; set; }
+ public string Liveliness { get; set; }
+ public int? LeaseDuration { get; set; }
+
+ internal static FixedFieldMap FixedFields => new()
+ {
+ { "reliability", (a, n) => { a.Reliability = n.GetScalarValue(); } },
+ { "history", (a, n) => { a.History = n.GetScalarValue(); } },
+ { "durability", (a, n) => { a.Durability = n.GetScalarValue(); } },
+ { "lifespan", (a, n) => { a.Lifespan = n.GetIntegerValueOrDefault(); } },
+ { "deadline", (a, n) => { a.Deadline = n.GetIntegerValueOrDefault(); } },
+ { "liveliness", (a, n) => { a.Liveliness = n.GetScalarValue(); } },
+ { "leaseDuration", (a, n) => { a.LeaseDuration = n.GetIntegerValueOrDefault(); } },
+ };
+
+ ///
+ /// Writes the QoS policy object, omitting unset values and preserving zero and negative numeric values.
+ ///
+ /// is null.
+ internal void Serialize(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("reliability", this.Reliability);
+ writer.WriteOptionalProperty("history", this.History);
+ writer.WriteOptionalProperty("durability", this.Durability);
+ writer.WriteOptionalProperty("lifespan", this.Lifespan);
+ writer.WriteOptionalProperty("deadline", this.Deadline);
+ writer.WriteOptionalProperty("liveliness", this.Liveliness);
+ writer.WriteOptionalProperty("leaseDuration", this.LeaseDuration);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2ServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2ServerBinding.cs
new file mode 100644
index 0000000..9512645
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/ROS2/ROS2ServerBinding.cs
@@ -0,0 +1,51 @@
+namespace ByteBard.AsyncAPI.Bindings.ROS2
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for ROS 2 server settings.
+ public class ROS2ServerBinding : ServerBinding
+ {
+ /// The ROS middleware implementation.
+ public string RmwImplementation { get; set; }
+
+ /// The ROS domain ID.
+ public int? DomainId { get; set; }
+
+ public override string BindingKey => "ros2";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "rmwImplementation", (a, n) => { a.RmwImplementation = n.GetScalarValue(); } },
+ { "domainId", (a, n) => { a.DomainId = n.GetIntegerValueOrDefault(); } },
+ };
+
+ ///
+ /// Writes the ROS 2 server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeV3(writer);
+
+ ///
+ /// Writes the ROS 2 server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the ROS 2 server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// BindingVersion is not written.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("rmwImplementation", this.RmwImplementation);
+ writer.WriteOptionalProperty("domainId", this.DomainId);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceDestination.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceDestination.cs
new file mode 100644
index 0000000..896c00f
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceDestination.cs
@@ -0,0 +1,42 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// A destination configured for a Solace operation.
+ public class SolaceDestination : IAsyncApiElement
+ {
+ public string BindingVersion { get; set; }
+ public string DestinationType { get; set; }
+ public string DeliveryMode { get; set; }
+ public SolaceQueue Queue { get; set; }
+ public SolaceTopic Topic { get; set; }
+
+ ///
+ /// Writes the destination object with any queue, topic, and binding version settings.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("destinationType", this.DestinationType);
+ writer.WriteOptionalProperty("deliveryMode", this.DeliveryMode);
+ writer.WriteOptionalObject("queue", this.Queue, (w, q) => q.Serialize(w));
+ writer.WriteOptionalObject("topic", this.Topic, (w, t) => t.Serialize(w));
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteEndObject();
+ }
+
+ internal static FixedFieldMap Fields => new()
+ {
+ { "bindingVersion", (a, n) => a.BindingVersion = n.GetScalarValue() },
+ { "destinationType", (a, n) => a.DestinationType = n.GetScalarValue() },
+ { "deliveryMode", (a, n) => a.DeliveryMode = n.GetScalarValue() },
+ { "queue", (a, n) => a.Queue = n.ParseMap(SolaceQueue.Fields) },
+ { "topic", (a, n) => a.Topic = n.ParseMap(SolaceTopic.Fields) },
+ };
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceIntegerOrSchema.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceIntegerOrSchema.cs
new file mode 100644
index 0000000..ee4b18d
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceIntegerOrSchema.cs
@@ -0,0 +1,83 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Represents a Solace integer, JSON Schema object, or JSON Schema reference.
+ public class SolaceIntegerOrSchema : IAsyncApiSerializable
+ {
+ private long? integer;
+ private AsyncApiJsonSchema schema;
+
+ public long? Integer
+ {
+ get => this.integer;
+ set
+ {
+ this.integer = value;
+ if (value.HasValue) this.schema = null;
+ }
+ }
+
+ public AsyncApiJsonSchema Schema
+ {
+ get => this.schema;
+ set
+ {
+ this.schema = value;
+ if (value != null) this.integer = null;
+ }
+ }
+
+ ///
+ /// Writes the integer or schema value directly for AsyncAPI 2.0, after validating that a value is present.
+ ///
+ /// is null.
+ /// Neither an integer nor a schema is set.
+ public void SerializeV2(IAsyncApiWriter writer) => this.Serialize(writer, false);
+ ///
+ /// Writes the integer or schema value directly for AsyncAPI 3.0, after validating that a value is present.
+ ///
+ /// is null.
+ /// Neither an integer nor a schema is set.
+ public void SerializeV3(IAsyncApiWriter writer) => this.Serialize(writer, true);
+
+ ///
+ /// Checks that an integer or schema is present, without validating its range or schema contents.
+ ///
+ /// Neither an integer nor a schema is set.
+ public void Validate()
+ {
+ if (!this.Integer.HasValue && this.Schema == null)
+ {
+ throw new InvalidOperationException("A Solace integer-or-schema value must contain an integer or a schema.");
+ }
+ }
+
+ ///
+ /// Validates and writes the integer or schema value without a property name or wrapper object.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 3.0 schema serialization when true and AsyncAPI 2.0 when false.
+ /// is null.
+ /// Neither an integer nor a schema is set.
+ private void Serialize(IAsyncApiWriter writer, bool v3)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ this.Validate();
+ if (this.Integer.HasValue)
+ {
+ writer.WriteValue(this.Integer.Value);
+ return;
+ }
+
+ if (this.Schema != null)
+ {
+ if (v3) this.Schema.SerializeV3(writer);
+ else this.Schema.SerializeV2(writer);
+ }
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceOperationBinding.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceOperationBinding.cs
new file mode 100644
index 0000000..4b3dc48
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceOperationBinding.cs
@@ -0,0 +1,93 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for Solace operations.
+ public class SolaceOperationBinding : OperationBinding
+ {
+ public List Destinations { get; set; } = new();
+ public SolaceIntegerOrSchema TimeToLive { get; set; }
+ public SolaceIntegerOrSchema Priority { get; set; }
+ public bool? DmqEligible { get; set; }
+ public override string BindingKey => "solace";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => a.BindingVersion = n.GetScalarValue() },
+ { "destinations", (a, n) => a.Destinations = n.CreateList(s => s.ParseMap(SolaceDestination.Fields)) },
+ { "timeToLive", (a, n) => a.TimeToLive = ReadIntegerOrSchema(n) },
+ { "priority", (a, n) => a.Priority = ReadIntegerOrSchema(n) },
+ { "dmqEligible", (a, n) => a.DmqEligible = n.GetBooleanValue() },
+ };
+
+ ///
+ /// Parses a scalar as a signed 64-bit integer, or an object as a schema or schema reference.
+ ///
+ /// A value containing the parsed integer or schema.
+ /// Schema field errors handled by the reader are recorded as diagnostics. Scalar parsing errors propagate to the caller.
+ /// The scalar cannot be parsed as an Int64, or a non-scalar node is not a schema object.
+ /// A schema reference has a non-scalar $ref value.
+ internal static SolaceIntegerOrSchema ReadIntegerOrSchema(ParseNode node)
+ {
+ if (node is ValueNode)
+ {
+ return new SolaceIntegerOrSchema { Integer = node.GetLongValue() };
+ }
+
+ return new SolaceIntegerOrSchema { Schema = AsyncApiJsonSchemaDeserializer.LoadSchema(node) };
+ }
+
+ ///
+ /// Writes the Solace operation binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ /// TimeToLive or Priority is present but contains neither an integer nor a schema.
+ public override void SerializeV2(IAsyncApiWriter writer) => Serialize(writer, false);
+ ///
+ /// Writes the Solace operation binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ /// TimeToLive or Priority is present but contains neither an integer nor a schema.
+ public override void SerializeV3(IAsyncApiWriter writer) => Serialize(writer, true);
+
+ ///
+ /// Writes the Solace operation binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// Validates the optional integer-or-schema values before writing the binding. Explicit false DmqEligible values are preserved.
+ /// is null.
+ /// TimeToLive or Priority is present but contains neither an integer nor a schema.
+ public override void SerializeProperties(IAsyncApiWriter writer) => Serialize(writer, false);
+
+ ///
+ /// Validates the optional integer-or-schema values, then writes the binding object and its extensions.
+ ///
+ /// The writer receiving the value.
+ /// Uses AsyncAPI 3.0 schema serialization when true and AsyncAPI 2.0 when false.
+ /// is null.
+ /// TimeToLive or Priority is present but contains neither an integer nor a schema.
+ private void Serialize(IAsyncApiWriter writer, bool v3)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ this.TimeToLive?.Validate();
+ this.Priority?.Validate();
+ writer.WriteStartObject();
+ writer.WriteOptionalCollection("destinations", this.Destinations, (w, d) => d.Serialize(w));
+ writer.WriteOptionalObject("timeToLive", this.TimeToLive, (w, value) => { if (v3) value.SerializeV3(w); else value.SerializeV2(w); });
+ writer.WriteOptionalObject("priority", this.Priority, (w, value) => { if (v3) value.SerializeV3(w); else value.SerializeV2(w); });
+ if (this.DmqEligible.HasValue)
+ {
+ writer.WritePropertyName("dmqEligible");
+ writer.WriteValue(this.DmqEligible.Value);
+ }
+
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceQueue.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceQueue.cs
new file mode 100644
index 0000000..ae1d61c
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceQueue.cs
@@ -0,0 +1,43 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Queue destination settings in a Solace operation binding.
+ public class SolaceQueue : IAsyncApiElement
+ {
+ public string Name { get; set; }
+ public List TopicSubscriptions { get; set; } = new();
+ public string AccessType { get; set; }
+ public string MaxMsgSpoolSize { get; set; }
+ public string MaxTtl { get; set; }
+
+ internal static FixedFieldMap Fields => new()
+ {
+ { "name", (a, n) => a.Name = n.GetScalarValue() },
+ { "topicSubscriptions", (a, n) => a.TopicSubscriptions = n.CreateSimpleList(s => s.GetScalarValue()) },
+ { "accessType", (a, n) => a.AccessType = n.GetScalarValue() },
+ { "maxMsgSpoolSize", (a, n) => a.MaxMsgSpoolSize = n.GetScalarValue() },
+ { "maxTtl", (a, n) => a.MaxTtl = n.GetScalarValue() },
+ };
+
+ ///
+ /// Writes the queue settings object, omitting unset values and empty topic subscriptions.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("name", this.Name);
+ writer.WriteOptionalCollection("topicSubscriptions", this.TopicSubscriptions, (w, s) => w.WriteValue(s));
+ writer.WriteOptionalProperty("accessType", this.AccessType);
+ writer.WriteOptionalProperty("maxMsgSpoolSize", this.MaxMsgSpoolSize);
+ writer.WriteOptionalProperty("maxTtl", this.MaxTtl);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceServerBinding.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceServerBinding.cs
new file mode 100644
index 0000000..d7756d1
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceServerBinding.cs
@@ -0,0 +1,48 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Binding class for Solace servers.
+ public class SolaceServerBinding : ServerBinding
+ {
+ public string MsgVpn { get; set; }
+ public string ClientName { get; set; }
+ public override string BindingKey => "solace";
+
+ protected override FixedFieldMap FixedFieldMap => new()
+ {
+ { "bindingVersion", (a, n) => a.BindingVersion = n.GetScalarValue() },
+ { "msgVpn", (a, n) => a.MsgVpn = n.GetScalarValue() },
+ { "clientName", (a, n) => a.ClientName = n.GetScalarValue() },
+ };
+
+ ///
+ /// Writes the Solace server binding object and its extensions for AsyncAPI 2.0.
+ ///
+ /// is null.
+ public override void SerializeV2(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+ ///
+ /// Writes the Solace server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// is null.
+ public override void SerializeV3(IAsyncApiWriter writer) => this.SerializeProperties(writer);
+
+ ///
+ /// Writes the Solace server binding object and its extensions for AsyncAPI 3.0.
+ ///
+ /// An unset binding version is omitted.
+ /// is null.
+ public override void SerializeProperties(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("msgVpn", this.MsgVpn);
+ writer.WriteOptionalProperty("clientName", this.ClientName);
+ writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
+ writer.WriteExtensions(this.Extensions);
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceTopic.cs b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceTopic.cs
new file mode 100644
index 0000000..f923b81
--- /dev/null
+++ b/src/ByteBard.AsyncAPI.Bindings/Solace/SolaceTopic.cs
@@ -0,0 +1,31 @@
+namespace ByteBard.AsyncAPI.Bindings.Solace
+{
+ using System;
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers.ParseNodes;
+ using ByteBard.AsyncAPI.Writers;
+
+ /// Topic destination settings in a Solace operation binding.
+ public class SolaceTopic : IAsyncApiElement
+ {
+ public List TopicSubscriptions { get; set; } = new();
+
+ internal static FixedFieldMap Fields => new()
+ {
+ { "topicSubscriptions", (a, n) => a.TopicSubscriptions = n.CreateSimpleList(s => s.GetScalarValue()) },
+ };
+
+ ///
+ /// Writes the topic settings object, omitting null or empty topic subscriptions.
+ ///
+ /// is null.
+ public void Serialize(IAsyncApiWriter writer)
+ {
+ if (writer is null) throw new ArgumentNullException(nameof(writer));
+ writer.WriteStartObject();
+ writer.WriteOptionalCollection("topicSubscriptions", this.TopicSubscriptions, (w, s) => w.WriteValue(s));
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/AnypointMQ/AnypointMQBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/AnypointMQ/AnypointMQBindings_Should.cs
new file mode 100644
index 0000000..50c9ad6
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/AnypointMQ/AnypointMQBindings_Should.cs
@@ -0,0 +1,47 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.AnypointMQ
+{
+ using ByteBard.AsyncAPI.Bindings.AnypointMQ;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using FluentAssertions;
+ using NUnit.Framework;
+
+ internal class AnypointMQBindings_Should : TestBase
+ {
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ChannelBinding_RoundTripsNestedPropertiesAndOmittedVersion(AsyncApiVersion version)
+ {
+ var original = new AsyncApiChannel();
+ original.Bindings.Add(new AnypointMQChannelBinding { Destination = "orders", DestinationType = "queue" });
+
+ var yaml = original.SerializeAsYaml(version);
+ yaml.Should().Contain("destination: orders").And.Contain("destinationType: queue").And.NotContain("bindingVersion");
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new AnypointMQChannelBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+
+ parsed.Bindings["anypointmq"].Should().BeEquivalentTo(original.Bindings["anypointmq"]);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void MessageBinding_RoundTripsHeadersSchema(AsyncApiVersion version)
+ {
+ var original = new AsyncApiMessage();
+ original.Bindings.Add(new AnypointMQMessageBinding
+ {
+ BindingVersion = "0.1.0",
+ Headers = new AsyncApiJsonSchema { Type = SchemaType.Object, Properties = { ["traceId"] = new AsyncApiJsonSchema { Type = SchemaType.String } } },
+ });
+
+ var yaml = original.SerializeAsYaml(version);
+ yaml.Should().Contain("headers:").And.Contain("traceId:").And.Contain("bindingVersion: 0.1.0");
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new AnypointMQMessageBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+
+ parsed.Bindings["anypointmq"].Should().BeEquivalentTo(original.Bindings["anypointmq"]);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/GooglePubSub/GooglePubSubBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/GooglePubSub/GooglePubSubBindings_Should.cs
new file mode 100644
index 0000000..2a9801e
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/GooglePubSub/GooglePubSubBindings_Should.cs
@@ -0,0 +1,56 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.GooglePubSub
+{
+ using System.Collections.Generic;
+ using FluentAssertions;
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.GooglePubSub;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.Interface;
+ using NUnit.Framework;
+
+ internal class GooglePubSubBindings_Should : TestBase
+ {
+ private static ICollection> Parsers => new List>
+ {
+ new GooglePubSubChannelBinding(), new GooglePubSubMessageBinding(),
+ };
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void GooglePubSubBindings_RoundTripShape(AsyncApiVersion version)
+ {
+ var channel = new AsyncApiChannel();
+ channel.Bindings.Add(new GooglePubSubChannelBinding
+ {
+ Labels = new Dictionary { ["team"] = "events" },
+ MessageRetentionDuration = "86400s",
+ MessageStoragePolicy = new MessageStoragePolicy { AllowedPersistenceRegions = new[] { "us-central1", "us-east1" } },
+ SchemaSettings = new SchemaSettings { Encoding = "JSON", FirstRevisionId = "1", LastRevisionId = "3", Name = "orders" },
+ });
+
+ var channelYaml = channel.SerializeAsYaml(version);
+ channelYaml.Should().Contain("messageRetentionDuration: 86400s")
+ .And.Contain("allowedPersistenceRegions:")
+ .And.Contain("firstRevisionId: '1'");
+ var settings = new AsyncApiReaderSettings { Bindings = Parsers };
+ var parsedChannel = new AsyncApiStringReader(settings).ReadFragment(channelYaml, version, out var channelDiagnostics);
+ channelDiagnostics.Errors.Should().BeEmpty();
+ parsedChannel.Should().BeEquivalentTo(channel);
+
+ var message = new AsyncApiMessage();
+ message.Bindings.Add(new GooglePubSubMessageBinding
+ {
+ Attributes = new Dictionary { ["source"] = "billing" },
+ OrderingKey = "customer-7",
+ Schema = new MessageSchema { Name = "projects/p/schemas/orders" },
+ });
+ var messageYaml = message.SerializeAsYaml(version);
+ messageYaml.Should().Contain("orderingKey: customer-7").And.Contain("projects/p/schemas/orders");
+ var parsedMessage = new AsyncApiStringReader(settings).ReadFragment(messageYaml, version, out var messageDiagnostics);
+ messageDiagnostics.Errors.Should().BeEmpty();
+ parsedMessage.Should().BeEquivalentTo(message);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/IBMMQ/IBMMQBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/IBMMQ/IBMMQBindings_Should.cs
new file mode 100644
index 0000000..3317a63
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/IBMMQ/IBMMQBindings_Should.cs
@@ -0,0 +1,91 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.IBMMQ
+{
+ using System.Collections.Generic;
+ using FluentAssertions;
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.IBMMQ;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.Interface;
+ using NUnit.Framework;
+
+ internal class IBMMQBindings_Should : TestBase
+ {
+ private static ICollection> Parsers => new List>
+ {
+ new IBMMQServerBinding(), new IBMMQChannelBinding(), new IBMMQMessageBinding(),
+ };
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void IBMMQBindings_RoundTripAndOmitUnsetValues(AsyncApiVersion version)
+ {
+ var server = new AsyncApiServer { Host = "mq.example", Protocol = "ibmmq" };
+ server.Bindings.Add(new IBMMQServerBinding
+ {
+ GroupId = "group-a", CcdtQueueManagerName = "QM1", CipherSpec = "TLS_AES_256_GCM_SHA384",
+ MultiEndpointServer = false, HeartBeatInterval = 0,
+ });
+ var serverYaml = server.SerializeAsYaml(version);
+ serverYaml.Should().Contain("multiEndpointServer: false").And.Contain("heartBeatInterval: 0");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(serverYaml, version, out _).Should().BeEquivalentTo(server);
+
+ var channel = new AsyncApiChannel();
+ channel.Bindings.Add(new IBMMQChannelBinding
+ {
+ DestinationType = "queue", MaxMsgLength = 0,
+ Queue = new Queue { ObjectName = "ORDERS", IsPartitioned = false, Exclusive = false },
+ });
+ var channelYaml = channel.SerializeAsYaml(version);
+ channelYaml.Should().Contain("isPartitioned: false").And.Contain("exclusive: false").And.Contain("maxMsgLength: 0").And.NotContain("topic:");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(channelYaml, version, out _).Should().BeEquivalentTo(channel);
+
+ var topicChannel = new AsyncApiChannel();
+ topicChannel.Bindings.Add(new IBMMQChannelBinding
+ {
+ DestinationType = "topic",
+ Topic = new Topic { StringValue = "orders/#", ObjectName = "ORDERS.TOPIC", DurablePermitted = false, LastMsgRetained = false },
+ });
+ var topicYaml = topicChannel.SerializeAsYaml(version);
+ topicYaml.Should().Contain("string: orders/#").And.Contain("durablePermitted: false").And.Contain("lastMsgRetained: false").And.NotContain("queue:");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(topicYaml, version, out _).Should().BeEquivalentTo(topicChannel);
+
+ var message = new AsyncApiMessage();
+ message.Bindings.Add(new IBMMQMessageBinding { Type = "binary", Headers = "STRING", Description = "event headers", Expiry = 2592000000L });
+ var messageYaml = message.SerializeAsYaml(version);
+ messageYaml.Should().Contain("headers: STRING").And.Contain("expiry: 2592000000");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(messageYaml, version, out _).Should().BeEquivalentTo(message);
+
+ var zeroExpiryMessage = new AsyncApiMessage();
+ zeroExpiryMessage.Bindings.Add(new IBMMQMessageBinding { Expiry = 0 });
+ var zeroExpiryYaml = zeroExpiryMessage.SerializeAsYaml(version);
+ zeroExpiryYaml.Should().Contain("expiry: 0");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(zeroExpiryYaml, version, out _).Should().BeEquivalentTo(zeroExpiryMessage);
+
+ var absent = new AsyncApiServer();
+ absent.Bindings.Add(new IBMMQServerBinding());
+ var absentYaml = absent.SerializeAsYaml(version);
+ absentYaml.Should().NotContain("multiEndpointServer").And.NotContain("heartBeatInterval").And.NotContain("bindingVersion");
+
+ var absentChannel = new AsyncApiChannel();
+ absentChannel.Bindings.Add(new IBMMQChannelBinding { Queue = new Queue() });
+ var absentChannelYaml = absentChannel.SerializeAsYaml(version);
+ absentChannelYaml.Should().NotContain("isPartitioned").And.NotContain("exclusive");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(absentChannelYaml, version, out _).Should().BeEquivalentTo(absentChannel);
+
+ var absentTopicChannel = new AsyncApiChannel();
+ absentTopicChannel.Bindings.Add(new IBMMQChannelBinding { Topic = new Topic() });
+ var absentTopicYaml = absentTopicChannel.SerializeAsYaml(version);
+ absentTopicYaml.Should().NotContain("durablePermitted").And.NotContain("lastMsgRetained");
+ new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = Parsers })
+ .ReadFragment(absentTopicYaml, version, out _).Should().BeEquivalentTo(absentTopicChannel);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/JMS/JMSBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/JMS/JMSBindings_Should.cs
new file mode 100644
index 0000000..6de032a
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/JMS/JMSBindings_Should.cs
@@ -0,0 +1,60 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.JMS
+{
+ using ByteBard.AsyncAPI.Bindings.JMS;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using FluentAssertions;
+ using NUnit.Framework;
+
+ internal class JMSBindings_Should : TestBase
+ {
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_RoundTripsNestedTypedPropertiesAndOmittedVersion(AsyncApiVersion version)
+ {
+ var original = new AsyncApiServer { Host = "localhost", Protocol = "jms" };
+ original.Bindings.Add(new JMSServerBinding
+ {
+ JmsConnectionFactory = "ConnectionFactory",
+ ClientID = "worker",
+ Properties = new[]
+ {
+ new JMSProperty { Name = "enabled", Value = new AsyncApiAny(false) },
+ new JMSProperty { Name = "priority", Value = new AsyncApiAny(0) },
+ new JMSProperty { Name = "region", Value = new AsyncApiAny("west") },
+ },
+ });
+
+ var yaml = original.SerializeAsYaml(version);
+ yaml.Should().Contain("jmsConnectionFactory: ConnectionFactory")
+ .And.Contain("clientID: worker").And.Contain("value: false").And.Contain("value: 0")
+ .And.NotContain("bindingVersion");
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new JMSServerBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, version, out _);
+
+ parsed.Bindings["jms"].Should().BeEquivalentTo(original.Bindings["jms"]);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ChannelAndMessageBindings_RoundTrip(AsyncApiVersion version)
+ {
+ var channel = new AsyncApiChannel();
+ channel.Bindings.Add(new JMSChannelBinding { Destination = "orders", DestinationType = "queue" });
+ var channelYaml = channel.SerializeAsYaml(version);
+ var channelSettings = new AsyncApiReaderSettings { Bindings = new[] { new JMSChannelBinding() } };
+ var parsedChannel = new AsyncApiStringReader(channelSettings).ReadFragment(channelYaml, version, out _);
+ parsedChannel.Bindings["jms"].Should().BeEquivalentTo(channel.Bindings["jms"]);
+
+ var message = new AsyncApiMessage();
+ message.Bindings.Add(new JMSMessageBinding
+ {
+ Headers = new AsyncApiJsonSchema { Type = SchemaType.Object, Properties = { ["correlationId"] = new AsyncApiJsonSchema { Type = SchemaType.String } } },
+ });
+ var messageYaml = message.SerializeAsYaml(version);
+ var messageSettings = new AsyncApiReaderSettings { Bindings = new[] { new JMSMessageBinding() } };
+ var parsedMessage = new AsyncApiStringReader(messageSettings).ReadFragment(messageYaml, version, out _);
+ parsedMessage.Bindings["jms"].Should().BeEquivalentTo(message.Bindings["jms"]);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs
index 53fb4b3..7b5eac9 100644
--- a/test/ByteBard.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/Kafka/KafkaBindings_Should.cs
@@ -1,6 +1,7 @@
namespace ByteBard.AsyncAPI.Tests.Bindings.Kafka
{
using System.Collections.Generic;
+ using System.Text.Json.Nodes;
using FluentAssertions;
using ByteBard.AsyncAPI.Bindings;
using ByteBard.AsyncAPI.Bindings.Kafka;
@@ -10,6 +11,39 @@
internal class KafkaBindings_Should : TestBase
{
+ [Test]
+ public void V2_KafkaTopicConfiguration_PreservesAdditionalScalarAndListValues()
+ {
+ var yaml = """
+ bindings:
+ kafka:
+ topicConfiguration:
+ custom.string: value
+ custom.integer: 12
+ custom.boolean: false
+ custom.list:
+ - one
+ - 2
+ - true
+ """;
+ var settings = new AsyncApiReaderSettings { Bindings = BindingsCollection.Kafka };
+ var reader = new AsyncApiStringReader(settings);
+
+ var channel = reader.ReadFragment(yaml, AsyncApiVersion.AsyncApi2_0, out var diagnostic);
+ diagnostic.Errors.Should().BeEmpty();
+
+ var serialized = channel.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0);
+ var roundTripped = reader.ReadFragment(serialized, AsyncApiVersion.AsyncApi2_0, out diagnostic);
+
+ diagnostic.Errors.Should().BeEmpty();
+ var configuration = ((KafkaChannelBinding)roundTripped.Bindings["kafka"]).TopicConfiguration;
+ configuration.AdditionalProperties["custom.string"].GetValue().Should().Be("value");
+ configuration.AdditionalProperties["custom.integer"].GetValue().Should().Be(12);
+ configuration.AdditionalProperties["custom.boolean"].GetValue().Should().BeFalse();
+ configuration.AdditionalProperties["custom.list"].GetNode().Should().BeOfType();
+ configuration.AdditionalProperties["custom.list"].GetNode().ToJsonString().Should().Be("[\"one\",2,true]");
+ }
+
[Test]
public void V2_KafkaChannelBinding_WithFilledObject_SerializesAndDeserializes()
{
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT/MQTTBindingVariants_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT/MQTTBindingVariants_Should.cs
new file mode 100644
index 0000000..8c3f241
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT/MQTTBindingVariants_Should.cs
@@ -0,0 +1,115 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.MQTT
+{
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.MQTT;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using FluentAssertions;
+ using NUnit.Framework;
+
+ public class MQTTBindingVariants_Should
+ {
+ [Test]
+ public void V2_SchemaAndReferenceVariants_AndBindingVersions_AreParsedAndSerialized()
+ {
+ var settings = new AsyncApiReaderSettings { Bindings = BindingsCollection.MQTT };
+ var serverYaml =
+ """
+ bindings:
+ mqtt:
+ sessionExpiryInterval:
+ type: integer
+ minimum: 1
+ maximumPacketSize:
+ $ref: '#/components/schemas/PacketSize'
+ bindingVersion: 0.2.0
+ """;
+ serverYaml = "url: localhost\nprotocol: mqtt\n" + serverYaml;
+ var server = new AsyncApiStringReader(settings).ReadFragment(serverYaml, AsyncApiVersion.AsyncApi2_0, out var serverDiagnostics);
+ serverDiagnostics.Errors.Should().BeEmpty();
+ var serverBinding = server.Bindings["mqtt"] as MQTTServerBinding;
+
+ serverBinding.BindingVersion.Should().Be("0.2.0");
+ serverBinding.SessionExpiryIntervalValue.Schema.Type.Should().Be(SchemaType.Integer);
+ serverBinding.MaximumPacketSizeValue.Schema.Should().BeOfType();
+ server.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0).Should().Contain("minimum: 1").And.Contain("$ref: '#/components/schemas/PacketSize'");
+
+ var operationYaml =
+ """
+ bindings:
+ mqtt:
+ qos: 1
+ retain: false
+ messageExpiryInterval:
+ $ref: '#/components/schemas/Expiry'
+ bindingVersion: 0.2.0
+ """;
+ var operation = new AsyncApiStringReader(settings).ReadFragment(operationYaml, AsyncApiVersion.AsyncApi2_0, out _);
+ var operationBinding = operation.Bindings["mqtt"] as MQTTOperationBinding;
+ operationBinding.BindingVersion.Should().Be("0.2.0");
+ operationBinding.MessageExpiryIntervalValue.Schema.Should().BeOfType();
+
+ var messageYaml =
+ """
+ bindings:
+ mqtt:
+ responseTopic:
+ type: string
+ pattern: '^reply/'
+ bindingVersion: 0.2.0
+ """;
+ var message = new AsyncApiStringReader(settings).ReadFragment(messageYaml, AsyncApiVersion.AsyncApi2_0, out _);
+ var messageBinding = message.Bindings["mqtt"] as MQTTMessageBinding;
+ messageBinding.BindingVersion.Should().Be("0.2.0");
+ messageBinding.ResponseTopicValue.Schema.Type.Should().Be(SchemaType.String);
+ message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0).Should().Contain("pattern: ^reply/");
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerSchemaOnlyBinding_RoundTripsWithoutDiagnostics(AsyncApiVersion version)
+ {
+ var settings = new AsyncApiReaderSettings { Bindings = BindingsCollection.MQTT };
+ var yaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + """
+ bindings:
+ mqtt:
+ sessionExpiryInterval:
+ type: integer
+ """;
+
+ var reader = new AsyncApiStringReader(settings);
+ var first = reader.ReadFragment(yaml, version, out var firstDiagnostics);
+ firstDiagnostics.Errors.Should().BeEmpty();
+ first.SerializeAsYaml(version).Should().NotContain("clientId: null");
+
+ var second = reader.ReadFragment(first.SerializeAsYaml(version), version, out var secondDiagnostics);
+ secondDiagnostics.Errors.Should().BeEmpty();
+ (second.Bindings["mqtt"] as MQTTServerBinding).SessionExpiryIntervalValue.Schema.Type.Should().Be(SchemaType.Integer);
+ }
+
+ [Test]
+ public void IntegerUnion_PreservesUnsigned32BitMaximum()
+ {
+ var binding = new MQTTServerBinding { SessionExpiryIntervalValue = MQTTIntegerOrSchema.FromLong(4294967295L) };
+ var yaml = binding.SerializeAsYaml(AsyncApiVersion.AsyncApi3_0);
+
+ yaml.Should().Contain("sessionExpiryInterval: 4294967295");
+ var parsedYaml = "host: localhost\nprotocol: mqtt\n" + """
+ bindings:
+ mqtt:
+ sessionExpiryInterval: 4294967295
+ """;
+ var parsed = new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = BindingsCollection.MQTT })
+ .ReadFragment(parsedYaml, AsyncApiVersion.AsyncApi3_0, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ (parsed.Bindings["mqtt"] as MQTTServerBinding).SessionExpiryIntervalValue.LongInteger.Should().Be(4294967295L);
+ }
+
+ [Test]
+ public void OperationBinding_OmitsUnsetQosAndRetain()
+ {
+ var yaml = new MQTTOperationBinding().SerializeAsYaml(AsyncApiVersion.AsyncApi3_0);
+ yaml.Should().NotContain("qos:").And.NotContain("retain:");
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT5/MQTT5Bindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT5/MQTT5Bindings_Should.cs
new file mode 100644
index 0000000..92fff0a
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/MQTT5/MQTT5Bindings_Should.cs
@@ -0,0 +1,88 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.MQTT5
+{
+ using FluentAssertions;
+ using ByteBard.AsyncAPI.Bindings.MQTT;
+ using ByteBard.AsyncAPI.Bindings.MQTT5;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using NUnit.Framework;
+
+ [System.Obsolete("The MQTT 5 server binding is deprecated.")]
+ internal class MQTT5Bindings_Should : TestBase
+ {
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_SerializesScalarZeroAndMinusOne(AsyncApiVersion version)
+ {
+ var binding = new MQTT5ServerBinding
+ {
+ SessionExpiryInterval = MQTTIntegerOrSchema.FromInteger(0),
+ BindingVersion = "0.1.0",
+ };
+ var actual = binding.SerializeAsYaml(version);
+ actual.Should().Contain("sessionExpiryInterval: 0").And.Contain("bindingVersion: 0.1.0");
+
+ binding.SessionExpiryInterval = MQTTIntegerOrSchema.FromInteger(-1);
+ actual = binding.SerializeAsYaml(version);
+ actual.Should().Contain("sessionExpiryInterval: -1");
+
+ binding.SessionExpiryInterval = MQTTIntegerOrSchema.FromLong(4294967295L);
+ binding.SerializeAsYaml(version).Should().Contain("sessionExpiryInterval: 4294967295");
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_SerializesSchemaAndReference(AsyncApiVersion version)
+ {
+ var schemaBinding = new MQTT5ServerBinding
+ {
+ SessionExpiryInterval = MQTTIntegerOrSchema.FromSchema(new AsyncApiJsonSchema { Type = SchemaType.Integer }),
+ };
+ schemaBinding.SerializeAsYaml(version).Should().Contain("type: integer");
+
+ var referenceBinding = new MQTT5ServerBinding
+ {
+ SessionExpiryInterval = MQTTIntegerOrSchema.FromSchema(new AsyncApiJsonSchemaReference("#/components/schemas/interval")),
+ };
+ referenceBinding.SerializeAsYaml(version).Should().Contain("#/components/schemas/interval");
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_ReadsAndRoundTripsSchemaAndReference(AsyncApiVersion version)
+ {
+ var parsers = ByteBard.AsyncAPI.Bindings.BindingsCollection.MQTT;
+ parsers.Add(new MQTT5ServerBinding());
+ var reader = new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = parsers });
+ var yaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + """
+ bindings:
+ mqtt5:
+ sessionExpiryInterval:
+ $ref: '#/components/schemas/interval'
+ """;
+
+ var parsed = reader.ReadFragment(yaml, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ var binding = parsed.Bindings["mqtt5"] as MQTT5ServerBinding;
+ binding.SessionExpiryInterval.Schema.Should().BeOfType();
+
+ var serialized = parsed.SerializeAsYaml(version);
+ serialized.Should().Contain("#/components/schemas/interval");
+ var reparsed = reader.ReadFragment(serialized, version, out var roundTripDiagnostics);
+ roundTripDiagnostics.Errors.Should().BeEmpty();
+ (reparsed.Bindings["mqtt5"] as MQTT5ServerBinding).SessionExpiryInterval.Schema.Should().BeOfType();
+
+ var schemaYaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + """
+ bindings:
+ mqtt5:
+ sessionExpiryInterval:
+ type: integer
+ minimum: 1
+ """;
+ var schemaParsed = reader.ReadFragment(schemaYaml, version, out var schemaDiagnostics);
+ schemaDiagnostics.Errors.Should().BeEmpty();
+ (schemaParsed.Bindings["mqtt5"] as MQTT5ServerBinding).SessionExpiryInterval.Schema.Type.Should().Be(SchemaType.Integer);
+ schemaParsed.SerializeAsYaml(version).Should().Contain("minimum: 1");
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/NATS/NatsBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/NATS/NatsBindings_Should.cs
new file mode 100644
index 0000000..4ad8386
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/NATS/NatsBindings_Should.cs
@@ -0,0 +1,31 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.NATS
+{
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.NATS;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.Interface;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using NUnit.Framework;
+ using FluentAssertions;
+
+ public class NatsBindings_Should
+ {
+ [Test]
+ public void OperationBinding_PreservesQueueAndOmittedVersion()
+ {
+ var operation = new AsyncApiOperation();
+ operation.Bindings.Add(new NATSOperationBinding { Queue = "workers" });
+
+ var yaml = operation.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0);
+ yaml.Should().Contain("queue: workers");
+ yaml.Should().NotContain("bindingVersion");
+
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new NATSOperationBinding() };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, AsyncApiVersion.AsyncApi2_0, out _);
+ parsed.Should().BeEquivalentTo(operation);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/NestedParsing/UnknownNestedField_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/NestedParsing/UnknownNestedField_Should.cs
new file mode 100644
index 0000000..2f5ee71
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/NestedParsing/UnknownNestedField_Should.cs
@@ -0,0 +1,38 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.NestedParsing
+{
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using FluentAssertions;
+ using NUnit.Framework;
+
+ internal class UnknownNestedField_Should : TestBase
+ {
+ [TestCase(UnmappedMemberHandling.Error, 1)]
+ [TestCase(UnmappedMemberHandling.Ignore, 0)]
+ public void FollowConfiguredUnmappedMemberHandling(UnmappedMemberHandling handling, int expectedErrorCount)
+ {
+ var yaml = """
+ bindings:
+ amqp:
+ is: routingKey
+ exchange:
+ name: events
+ unknownNestedField: ignored-value
+ """;
+ var settings = new AsyncApiReaderSettings
+ {
+ Bindings = BindingsCollection.AMQP,
+ UnmappedMemberHandling = handling,
+ };
+
+ new AsyncApiStringReader(settings).ReadFragment(yaml, AsyncApiVersion.AsyncApi2_0, out var diagnostic);
+
+ diagnostic.Errors.Should().HaveCount(expectedErrorCount);
+ if (expectedErrorCount > 0)
+ {
+ diagnostic.Errors[0].Message.Should().Contain("unknownNestedField");
+ }
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/ROS2/ROS2Bindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/ROS2/ROS2Bindings_Should.cs
new file mode 100644
index 0000000..1646dd2
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/ROS2/ROS2Bindings_Should.cs
@@ -0,0 +1,114 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.ROS2
+{
+ using FluentAssertions;
+ using ByteBard.AsyncAPI.Bindings.ROS2;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using NUnit.Framework;
+
+ internal class ROS2Bindings_Should : TestBase
+ {
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void OperationBinding_WritesNestedQosAndRetainsZeroAndMinusOne(AsyncApiVersion version)
+ {
+ var binding = new ROS2OperationBinding
+ {
+ Role = "publisher",
+ Node = "camera",
+ QosPolicies = new ROS2QosPolicies
+ {
+ Reliability = "reliable",
+ History = "keep_last",
+ Durability = "transient_local",
+ Lifespan = 0,
+ Deadline = -1,
+ Liveliness = "automatic",
+ LeaseDuration = 0,
+ },
+ };
+
+ var operation = new AsyncApiOperation();
+ operation.Channel = new AsyncApiChannelReference("#/channels/camera");
+ operation.Messages.Add(new AsyncApiMessageReference("#/components/messages/camera"));
+ operation.Bindings.Add(binding);
+ var actual = operation.SerializeAsYaml(version);
+ actual.Should().Contain("role: publisher")
+ .And.Contain("qosPolicies:")
+ .And.Contain("history: keep_last")
+ .And.Contain("durability: transient_local")
+ .And.Contain("lifespan: 0")
+ .And.Contain("deadline: -1")
+ .And.Contain("leaseDuration: 0")
+ .And.NotContain("bindingVersion:");
+
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new ROS2OperationBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(actual, version, out var diagnostics);
+ if (version == AsyncApiVersion.AsyncApi2_0)
+ {
+ diagnostics.Errors.Should().ContainSingle().Which.ToString().Should().Contain("field 'channel'");
+ }
+ else
+ {
+ diagnostics.Errors.Should().BeEmpty();
+ }
+ diagnostics.Warnings.Should().BeEmpty();
+ parsed.Bindings["ros2"].Should().BeEquivalentTo(binding);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_OmitsUnsetFieldsAndWritesExplicitZero(AsyncApiVersion version)
+ {
+ var binding = new ROS2ServerBinding { DomainId = 0 };
+ var server = new AsyncApiServer { Host = "localhost", Protocol = "ros2" };
+ server.Bindings.Add(binding);
+ var actual = server.SerializeAsYaml(version);
+ actual.Should().Contain("domainId: 0")
+ .And.NotContain("rmwImplementation:")
+ .And.NotContain("bindingVersion:");
+
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new ROS2ServerBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(actual, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ diagnostics.Warnings.Should().BeEmpty();
+ parsed.Bindings["ros2"].Should().BeEquivalentTo(binding);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_RoundTripsUpperBoundaryDomainId(AsyncApiVersion version)
+ {
+ var binding = new ROS2ServerBinding { DomainId = 231 };
+ var server = new AsyncApiServer { Host = "localhost", Protocol = "ros2" };
+ server.Bindings.Add(binding);
+
+ var actual = server.SerializeAsYaml(version);
+ actual.Should().Contain("domainId: 231");
+
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new ROS2ServerBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(actual, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ diagnostics.Warnings.Should().BeEmpty();
+ parsed.Bindings["ros2"].Should().BeEquivalentTo(binding);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_OmitsUnsetDomainId(AsyncApiVersion version)
+ {
+ var binding = new ROS2ServerBinding();
+ var server = new AsyncApiServer { Host = "localhost", Protocol = "ros2" };
+ server.Bindings.Add(binding);
+
+ var actual = server.SerializeAsYaml(version);
+ actual.Should().NotContain("domainId:");
+
+ var settings = new AsyncApiReaderSettings { Bindings = new[] { new ROS2ServerBinding() } };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(actual, version, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ diagnostics.Warnings.Should().BeEmpty();
+ parsed.Bindings["ros2"].Should().BeEquivalentTo(binding);
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/Registration/BindingsCollection_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/Registration/BindingsCollection_Should.cs
new file mode 100644
index 0000000..11c19e2
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/Registration/BindingsCollection_Should.cs
@@ -0,0 +1,104 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.Registration
+{
+ using System.Linq;
+ using FluentAssertions;
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.NATS;
+ using ByteBard.AsyncAPI.Bindings.ROS2;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.Interface;
+ using NUnit.Framework;
+
+ internal class BindingsCollection_Should : TestBase
+ {
+ [Test]
+ public void All_contains_each_protocol_and_scope_once_and_matches_families()
+ {
+ var families = new[]
+ {
+ BindingsCollection.Pulsar, BindingsCollection.Kafka, BindingsCollection.Http, BindingsCollection.Websockets,
+ BindingsCollection.Sqs, BindingsCollection.Sns, BindingsCollection.AMQP, BindingsCollection.MQTT,
+ BindingsCollection.AnypointMQ, BindingsCollection.GooglePubSub, BindingsCollection.IBMMQ, BindingsCollection.JMS,
+ BindingsCollection.MQTT5, BindingsCollection.NATS, BindingsCollection.ROS2, BindingsCollection.Solace,
+ };
+ var all = BindingsCollection.All.ToArray();
+
+ families.Should().HaveCount(16);
+ all.Should().HaveCount(35);
+ all.Should().HaveCount(families.Sum(family => family.Count));
+ all.Select(parser => parser.GetType()).Should().OnlyHaveUniqueItems();
+ all.GroupBy(parser =>
+ {
+ var instance = (IBinding)System.Activator.CreateInstance(parser.GetType())!;
+ var scope = instance is IServerBinding ? "server" : instance is IChannelBinding ? "channel" :
+ instance is IOperationBinding ? "operation" : "message";
+ return (instance.BindingKey, scope);
+ }).Should().OnlyContain(group => group.Count() == 1);
+ families.SelectMany(family => family).Select(parser => parser.GetType())
+ .Should().BeEquivalentTo(all.Select(parser => parser.GetType()));
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void Reader_uses_all_to_parse_field_and_new_server_bindings(AsyncApiVersion version)
+ {
+ var reader = new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = BindingsCollection.All });
+ var serverAddress = version == AsyncApiVersion.AsyncApi2_0
+ ? "url: broker.example\n"
+ : "host: broker.example\n";
+ var fieldBinding = reader.ReadFragment(serverAddress + "protocol: ros2\n" +
+ "bindings:\n ros2:\n rmwImplementation: rmw_fastrtps_cpp\n domainId: 7\n", version, out var ros2Diagnostics);
+ ros2Diagnostics.Errors.Should().BeEmpty();
+ ros2Diagnostics.Warnings.Should().BeEmpty();
+
+ var ros2 = fieldBinding.Bindings["ros2"].Should().BeOfType().Subject;
+ ros2.RmwImplementation.Should().Be("rmw_fastrtps_cpp");
+ ros2.DomainId.Should().Be(7);
+
+ var mqtt5Binding = reader.ReadFragment(serverAddress + "protocol: mqtt5\nbindings:\n mqtt5: {}\n", version, out var mqtt5Diagnostics);
+ mqtt5Diagnostics.Errors.Should().BeEmpty();
+ mqtt5Diagnostics.Warnings.Should().BeEmpty();
+ mqtt5Binding.Bindings.Should().ContainSingle(binding => binding.Key == "mqtt5");
+
+ var operationYaml = version == AsyncApiVersion.AsyncApi2_0
+ ? """
+ asyncapi: 2.6.0
+ info:
+ title: Registration test
+ version: 1.0.0
+ channels:
+ orders:
+ publish:
+ operationId: consumeOrders
+ bindings:
+ nats:
+ queue: workers
+ """
+ : """
+ asyncapi: 3.0.0
+ info:
+ title: Registration test
+ version: 1.0.0
+ channels:
+ orders:
+ address: orders
+ operations:
+ consumeOrders:
+ action: send
+ channel:
+ $ref: '#/channels/orders'
+ bindings:
+ nats:
+ queue: workers
+ """;
+ var operationDocument = reader.Read(operationYaml, out var operationDiagnostics);
+ operationDiagnostics.Errors.Should().BeEmpty();
+ operationDiagnostics.Warnings.Should().BeEmpty();
+ operationDocument.Channels.Should().ContainKey("orders");
+ operationDocument.Operations["consumeOrders"].Bindings["nats"].Should().BeOfType()
+ .Which.Queue.Should().Be("workers");
+ }
+ }
+}
diff --git a/test/ByteBard.AsyncAPI.Tests/Bindings/Solace/SolaceBindings_Should.cs b/test/ByteBard.AsyncAPI.Tests/Bindings/Solace/SolaceBindings_Should.cs
new file mode 100644
index 0000000..342b08d
--- /dev/null
+++ b/test/ByteBard.AsyncAPI.Tests/Bindings/Solace/SolaceBindings_Should.cs
@@ -0,0 +1,199 @@
+namespace ByteBard.AsyncAPI.Tests.Bindings.Solace
+{
+ using System.Collections.Generic;
+ using ByteBard.AsyncAPI.Bindings;
+ using ByteBard.AsyncAPI.Bindings.Solace;
+ using ByteBard.AsyncAPI.Models;
+ using ByteBard.AsyncAPI.Readers;
+ using ByteBard.AsyncAPI.Readers.Interface;
+ using ByteBard.AsyncAPI.Models.Interfaces;
+ using NUnit.Framework;
+ using FluentAssertions;
+
+ public class SolaceBindings_Should
+ {
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void OperationBinding_RoundTripsNestedDestinationsAndScalarUnionValues(AsyncApiVersion version)
+ {
+ var operation = new AsyncApiOperation();
+ operation.Bindings.Add(new SolaceOperationBinding
+ {
+ DmqEligible = false,
+ TimeToLive = new SolaceIntegerOrSchema { Integer = 0 },
+ Priority = new SolaceIntegerOrSchema { Integer = 7 },
+ Destinations =
+ {
+ new SolaceDestination
+ {
+ DestinationType = "queue",
+ DeliveryMode = "persistent",
+ Queue = new SolaceQueue
+ {
+ Name = "orders",
+ AccessType = "exclusive",
+ MaxMsgSpoolSize = "100",
+ MaxTtl = "3600",
+ TopicSubscriptions = { "orders/>" },
+ },
+ },
+ },
+ });
+
+ var yaml = operation.SerializeAsYaml(version);
+ yaml.Should().Contain("dmqEligible: false").And.Contain("timeToLive: 0").And.Contain("maxTtl:");
+
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceOperationBinding() };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, version, out _);
+ parsed.Should().BeEquivalentTo(operation);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void OperationBinding_RoundTripsSchemaAndReferenceUnionValues(AsyncApiVersion version)
+ {
+ var operation = new AsyncApiOperation
+ {
+ Action = AsyncApiAction.Send,
+ Channel = new AsyncApiChannelReference("#/channels/orders"),
+ };
+ operation.Bindings.Add(new SolaceOperationBinding
+ {
+ TimeToLive = new SolaceIntegerOrSchema
+ {
+ Schema = new AsyncApiJsonSchema { Description = "ttl schema" },
+ },
+ Priority = new SolaceIntegerOrSchema
+ {
+ Schema = new AsyncApiJsonSchemaReference("#/components/schemas/Priority"),
+ },
+ });
+
+ var document = new AsyncApiDocument
+ {
+ Info = new AsyncApiInfo { Title = "Solace union fixture", Version = "1.0.0" },
+ };
+ document.Channels.Add("orders", new AsyncApiChannel { Address = "orders" });
+ document.Operations.Add("solaceOperation", operation);
+ document.Components.Schemas.Add("Priority", new AsyncApiMultiFormatSchema
+ {
+ Schema = new AsyncApiJsonSchema { Type = SchemaType.Integer },
+ });
+
+ var yaml = document.SerializeAsYaml(version);
+ yaml.Should().Contain("description: ttl schema").And.Contain("#/components/schemas/Priority");
+
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceOperationBinding() };
+ var reader = new AsyncApiStringReader(settings);
+ var parsed = reader.Read(yaml, out var diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ parsed.Operations.Should().ContainSingle();
+
+ var rewritten = parsed.SerializeAsYaml(version);
+ var reparsed = reader.Read(rewritten, out diagnostics);
+ diagnostics.Errors.Should().BeEmpty();
+ var binding = reparsed.Operations.Values.Should().ContainSingle().Which.Bindings.Values.Should()
+ .ContainSingle().Which.Should().BeOfType().Subject;
+ binding.TimeToLive.Schema.Description.Should().Be("ttl schema");
+ ((AsyncApiJsonSchemaReference)binding.Priority.Schema).Reference.Reference.Should().Be("#/components/schemas/Priority");
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void OperationBinding_RoundTripsLargeTimeToLive(AsyncApiVersion version)
+ {
+ const long largeTtl = 2592000000L;
+ var operation = new AsyncApiOperation();
+ operation.Bindings.Add(new SolaceOperationBinding
+ {
+ TimeToLive = new SolaceIntegerOrSchema { Integer = largeTtl },
+ });
+
+ var yaml = operation.SerializeAsYaml(version);
+ yaml.Should().Contain("timeToLive: 2592000000");
+
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceOperationBinding() };
+ var parsed = new AsyncApiStringReader(settings).ReadFragment(yaml, version, out _);
+ var binding = parsed.Bindings.Should().ContainSingle().Which.Value.Should().BeOfType().Subject;
+ binding.TimeToLive.Integer.Should().Be(largeTtl);
+ }
+
+ [Test]
+ public void OperationBinding_RejectsEmptyIntegerOrSchemaBeforeWriting()
+ {
+ var operation = new AsyncApiOperation();
+ operation.Bindings.Add(new SolaceOperationBinding
+ {
+ TimeToLive = new SolaceIntegerOrSchema(),
+ });
+
+ var exception = FluentActions.Invoking(() => operation.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0))
+ .Should().Throw()
+ .Which;
+ exception.Message.Should().Contain("must contain an integer or a schema");
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void OperationBinding_ReportsIntegerOutsideInt64AsReaderDiagnostic(AsyncApiVersion version)
+ {
+ const string yaml = "bindings:\n solace:\n timeToLive: 9223372036854775808\n";
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceOperationBinding() };
+
+ new AsyncApiStringReader(settings)
+ .ReadFragment(yaml, version, out var diagnostics);
+ diagnostics.Errors.Should().NotBeEmpty();
+ diagnostics.Errors.Should().Contain(error => error.Message.Contains("could not parse to long"));
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ServerBinding_RoundTripsFields(AsyncApiVersion version)
+ {
+ var yaml = version == AsyncApiVersion.AsyncApi2_0
+ ? "url: localhost\nprotocol: solace\nbindings:\n solace:\n msgVpn: orders\n clientName: consumer\n"
+ : "host: localhost\nprotocol: solace\nbindings:\n solace:\n msgVpn: orders\n clientName: consumer\n";
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceServerBinding() };
+ var reader = new AsyncApiStringReader(settings);
+ var parsed = reader.ReadFragment(yaml, version, out _);
+ var rewritten = parsed.SerializeAsYaml(version);
+ var reparsed = reader.ReadFragment(rewritten, version, out _);
+ reparsed.Bindings.Should().BeEquivalentTo(parsed.Bindings);
+ }
+
+ [TestCase(AsyncApiVersion.AsyncApi2_0)]
+ [TestCase(AsyncApiVersion.AsyncApi3_0)]
+ public void ParsedOperationBinding_RoundTripsNestedQueueAndTopicDestinations(AsyncApiVersion version)
+ {
+ const string yaml = """
+ bindings:
+ solace:
+ destinations:
+ - destinationType: queue
+ deliveryMode: direct
+ queue:
+ name: orders
+ topicSubscriptions: [orders/>]
+ accessType: nonexclusive
+ maxMsgSpoolSize: 500
+ maxTtl: 900
+ - destinationType: topic
+ topic:
+ topicSubscriptions: [audit/>]
+ """;
+ var settings = new AsyncApiReaderSettings();
+ settings.Bindings = new List> { new SolaceOperationBinding() };
+ var reader = new AsyncApiStringReader(settings);
+ var parsed = reader.ReadFragment(yaml, version, out _);
+ var rewritten = parsed.SerializeAsYaml(version);
+ var reparsed = reader.ReadFragment(rewritten, version, out _);
+
+ reparsed.Should().BeEquivalentTo(parsed);
+ }
+ }
+}