Skip to content

Commit 785bad7

Browse files
bieluclaude
andcommitted
fix: keep mqtt5 and mercure as AsyncAPI 3 binding names
The AsyncAPI 3.0 and 3.1 specification text lists mqtt5 and mercure as fixed fields of the bindings objects; only the JSON schemas leave them out. Follow the normative text, so mqtt5 is written and read under its own key in 3.x instead of x-mqtt5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8b93442 commit 785bad7

6 files changed

Lines changed: 14 additions & 17 deletions

File tree

‎src/ByteBard.AsyncAPI/Models/AsyncApiBindingKeys.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
/// </remarks>
1616
public static class AsyncApiBindingKeys
1717
{
18-
// Every binding named by a bindings object in the AsyncAPI 3.0 and 3.1 JSON schemas.
18+
// Every binding the AsyncAPI 3.0 and 3.1 specifications name in a bindings object. The prose is normative; the
19+
// JSON schemas omit mqtt5 and mercure, but the specification text lists them as fixed fields.
1920
private static readonly HashSet<string> SpecificationKeys = new HashSet<string>(StringComparer.Ordinal)
2021
{
21-
"amqp", "amqp1", "anypointmq", "googlepubsub", "http", "ibmmq", "jms", "kafka", "mqtt", "nats", "pulsar",
22-
"redis", "ros2", "sns", "solace", "sqs", "stomp", "ws",
22+
"amqp", "amqp1", "anypointmq", "googlepubsub", "http", "ibmmq", "jms", "kafka", "mercure", "mqtt", "mqtt5",
23+
"nats", "pulsar", "redis", "ros2", "sns", "solace", "sqs", "stomp", "ws",
2324
};
2425

2526
/// <summary>

‎test/ByteBard.AsyncAPI.Tests/Bindings/CustomBindingKey_Should.cs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ public void BindingKeyWithExtensionPrefix_ReadsAndWritesAsBinding(AsyncApiVersio
174174
[TestCase("kafka", true)]
175175
[TestCase("ws", true)]
176176
[TestCase("ros2", true)]
177-
[TestCase("mqtt5", false)]
177+
[TestCase("mqtt5", true)]
178+
[TestCase("mercure", true)]
178179
[TestCase("signalr", false)]
179180
[TestCase("x-signalr", false)]
180181
public void IsSpecificationKey_ReturnsWhetherAsyncApi3DefinesTheBinding(string key, bool expected)
@@ -185,7 +186,7 @@ public void IsSpecificationKey_ReturnsWhetherAsyncApi3DefinesTheBinding(string k
185186
[TestCase("kafka", "kafka")]
186187
[TestCase("signalr", "x-signalr")]
187188
[TestCase("x-signalr", "x-signalr")]
188-
[TestCase("mqtt5", "x-mqtt5")]
189+
[TestCase("mqtt5", "mqtt5")]
189190
public void ToV3Key_PrefixesKeysAsyncApi3DoesNotDefine(string key, string expected)
190191
{
191192
AsyncApiBindingKeys.ToV3Key(key).Should().Be(expected);

‎test/ByteBard.AsyncAPI.Tests/Bindings/MQTT5/MQTT5Bindings_Should.cs‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,9 @@ public void ServerBinding_ReadsAndRoundTripsSchemaAndReference(AsyncApiVersion v
5454
var parsers = ByteBard.AsyncAPI.Bindings.BindingsCollection.MQTT;
5555
parsers.Add(new MQTT5ServerBinding());
5656
var reader = new AsyncApiStringReader(new AsyncApiReaderSettings { Bindings = parsers });
57-
58-
// AsyncAPI 3 dropped the mqtt5 binding, so a 3.x document carries it as an extension.
59-
var key = version == AsyncApiVersion.AsyncApi2_0 ? "mqtt5" : "x-mqtt5";
60-
var yaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + $$"""
57+
var yaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + """
6158
bindings:
62-
{{key}}:
59+
mqtt5:
6360
sessionExpiryInterval:
6461
$ref: '#/components/schemas/interval'
6562
""";
@@ -75,9 +72,9 @@ public void ServerBinding_ReadsAndRoundTripsSchemaAndReference(AsyncApiVersion v
7572
roundTripDiagnostics.Errors.Should().BeEmpty();
7673
(reparsed.Bindings["mqtt5"] as MQTT5ServerBinding).SessionExpiryInterval.Should().BeOfType<MQTTIntegerSchema>().Which.Schema.Should().BeOfType<AsyncApiJsonSchemaReference>();
7774

78-
var schemaYaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + $$"""
75+
var schemaYaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url: localhost\nprotocol: mqtt\n" : "host: localhost\nprotocol: mqtt\n") + """
7976
bindings:
80-
{{key}}:
77+
mqtt5:
8178
sessionExpiryInterval:
8279
type: integer
8380
minimum: 1

‎test/ByteBard.AsyncAPI.Tests/Bindings/Registration/BindingsCollection_Should.cs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ public void Reader_uses_all_to_parse_field_and_new_server_bindings(AsyncApiVersi
5757
ros2.RmwImplementation.Should().Be("rmw_fastrtps_cpp");
5858
ros2.DomainId.Should().Be(7);
5959

60-
// AsyncAPI 3 dropped the mqtt5 binding, so a 3.x document carries it as an extension.
61-
var mqtt5Key = version == AsyncApiVersion.AsyncApi2_0 ? "mqtt5" : "x-mqtt5";
62-
var mqtt5Binding = reader.ReadFragment<AsyncApiServer>(serverAddress + "protocol: mqtt5\nbindings:\n " + mqtt5Key + ": {}\n", version, out var mqtt5Diagnostics);
60+
var mqtt5Binding = reader.ReadFragment<AsyncApiServer>(serverAddress + "protocol: mqtt5\nbindings:\n mqtt5: {}\n", version, out var mqtt5Diagnostics);
6361
mqtt5Diagnostics.Errors.Should().BeEmpty();
6462
mqtt5Diagnostics.Warnings.Should().BeEmpty();
6563
mqtt5Binding.Bindings.Should().ContainSingle(binding => binding.Key == "mqtt5");

‎website/docs/bindings/mqtt5.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ if (referenceServer.Bindings.TryGetValue<MQTT5ServerBinding>(out var referenced)
8787

8888
`TryGetValue<TBinding>` returns `false` if no binding with that type's `BindingKey` is present; the union pattern then distinguishes the scalar and schema values.
8989

90-
The union is also used by MQTT's server and operation fields. MQTT 5's binding is deprecated, and `BindingsCollection.MQTT5` has no channel, operation, or message parser. AsyncAPI 2.x and 3.x serialization are supported; specify a target version when serializing. AsyncAPI 3 has no `mqtt5` binding, so a 3.x document carries it as `x-mqtt5` (see [custom bindings](/docs/guides/custom-bindings)). See the [shared guide](index.md).
90+
The union is also used by MQTT's server and operation fields. MQTT 5's binding is deprecated, and `BindingsCollection.MQTT5` has no channel, operation, or message parser. AsyncAPI 2.x and 3.x serialization are supported; specify a target version when serializing. See the [shared guide](index.md).
9191

9292
## Object model
9393

‎website/docs/guides/custom-bindings.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ var document = new AsyncApiStringReader(settings).Read(input, out var diagnostic
5151

5252
## Binding keys in AsyncAPI 3
5353

54-
AsyncAPI 3 bindings objects only allow the binding names the specification lists (`amqp`, `http`, `kafka`, `ws` and so on) and `x-` extensions. When a document is serialized as AsyncAPI 3, a binding whose `BindingKey` is not one of those names is written as an extension: `FooBinding` above appears as `x-foo`. The key in the object model stays `foo`, so `channel.Bindings["foo"]` and `TryGetValue<FooBinding>` work as before. `AsyncApiBindingKeys.ToV3Key` returns the name a key is written under. AsyncAPI 2 allows any key, so AsyncAPI 2 documents keep `foo`. A binding whose `BindingKey` already starts with `x-` is written and read under that key in both versions.
54+
AsyncAPI 3 bindings objects only allow the binding names the specification lists (`amqp`, `http`, `kafka`, `ws` and so on) and `x-` extensions. The list follows the specification text, which also names `mqtt5` and `mercure` even though the AsyncAPI 3 JSON schemas leave them out. When a document is serialized as AsyncAPI 3, a binding whose `BindingKey` is not one of those names is written as an extension: `FooBinding` above appears as `x-foo`. The key in the object model stays `foo`, so `channel.Bindings["foo"]` and `TryGetValue<FooBinding>` work as before. `AsyncApiBindingKeys.ToV3Key` returns the name a key is written under. AsyncAPI 2 allows any key, so AsyncAPI 2 documents keep `foo`. A binding whose `BindingKey` already starts with `x-` is written and read under that key in both versions.
5555

5656
When reading an AsyncAPI 3 document, `x-foo` is read as the binding if a parser for `foo` is registered; otherwise it stays an extension. A registered custom binding under its bare key (`foo`) is still read, but the reader reports an error, since the document does not conform to the specification.
5757

0 commit comments

Comments
 (0)