Skip to content

Commit 04dc7cb

Browse files
committed
Refactors binding serialization for AsyncAPI v2/v3
Consolidates binding serialization logic for AsyncAPI v2 and v3. The change introduces `SerializeV2` and `SerializeV3` methods in binding classes, deprecating the old `SerializeProperties` to streamline the serialization process. Additionally, it adds a `SerializationContext` to the `AsyncApiWorkspace` to enable bindings to access parent context during serialization. This addresses inconsistencies in binding serialization across different AsyncAPI versions, and allows http bindings to serialize according to the spec.
1 parent 7085d23 commit 04dc7cb

32 files changed

Lines changed: 631 additions & 218 deletions

src/ByteBard.AsyncAPI.Bindings/AMQP/AMQPChannelBinding.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ public class AMQPChannelBinding : ChannelBinding<AMQPChannelBinding>
5353
{ "vhost", (a, n) => { a.Vhost = n.GetScalarValue(); } },
5454
};
5555

56-
/// <summary>
57-
/// Serialize to AsyncAPI V2 document without using reference.
58-
/// </summary>
56+
public override void SerializeV2(IAsyncApiWriter writer)
57+
{
58+
this.SerializeV3(writer);
59+
}
60+
61+
public override void SerializeV3(IAsyncApiWriter writer)
62+
{
63+
this.SerializeProperties(writer);
64+
}
65+
5966
public override void SerializeProperties(IAsyncApiWriter writer)
6067
{
6168
if (writer is null)

src/ByteBard.AsyncAPI.Bindings/AMQP/AMQPMessageBinding.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ public class AMQPMessageBinding : MessageBinding<AMQPMessageBinding>
2020
/// </summary>
2121
public string MessageType { get; set; }
2222

23+
public override void SerializeV2(IAsyncApiWriter writer)
24+
{
25+
this.SerializeV3(writer);
26+
}
27+
28+
public override void SerializeV3(IAsyncApiWriter writer)
29+
{
30+
this.SerializeProperties(writer);
31+
}
32+
2333
public override void SerializeProperties(IAsyncApiWriter writer)
2434
{
2535
if (writer is null)

src/ByteBard.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,16 @@ public class AMQPOperationBinding : OperationBinding<AMQPOperationBinding>
7272
{ "ack", (a, n) => { a.Ack = n.GetBooleanValue(); } },
7373
};
7474

75-
/// <summary>
76-
/// Serialize to AsyncAPI V2 document without using reference.
77-
/// </summary>
75+
public override void SerializeV2(IAsyncApiWriter writer)
76+
{
77+
this.SerializeV3(writer);
78+
}
79+
80+
public override void SerializeV3(IAsyncApiWriter writer)
81+
{
82+
this.SerializeProperties(writer);
83+
}
84+
7885
public override void SerializeProperties(IAsyncApiWriter writer)
7986
{
8087
if (writer is null)
Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace ByteBard.AsyncAPI.Bindings.Http
22
{
33
using System;
4+
using System.Net;
45
using ByteBard.AsyncAPI.Models;
56
using ByteBard.AsyncAPI.Readers;
67
using ByteBard.AsyncAPI.Readers.ParseNodes;
@@ -9,38 +10,71 @@ namespace ByteBard.AsyncAPI.Bindings.Http
910
/// <summary>
1011
/// Binding class for http messaging channels.
1112
/// </summary>
13+
/// <remarks>
14+
/// The 'statusCode' field exists in AsyncAPI V3 but not in V2.
15+
/// </remarks>
1216
public class HttpMessageBinding : MessageBinding<HttpMessageBinding>
1317
{
18+
private const string V2BindingVersion = "0.2.0";
19+
private const string V3BindingVersion = "0.3.0";
20+
1421
/// <summary>
1522
/// A Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type object and have a properties key.
1623
/// </summary>
1724
public AsyncApiJsonSchema Headers { get; set; }
1825

1926
/// <summary>
20-
/// Serialize to AsyncAPI V2 document without using reference.
27+
/// The HTTP response status code according to RFC 9110. `statusCode` is only relevant for messages referenced by the Operation Reply Object.
28+
/// Note: This field is only serialized in AsyncAPI V3.
2129
/// </summary>
22-
public override void SerializeProperties(IAsyncApiWriter writer)
30+
public HttpStatusCode? StatusCode { get; set; }
31+
32+
public override string BindingKey => "http";
33+
34+
protected override FixedFieldMap<HttpMessageBinding> FixedFieldMap => new()
35+
{
36+
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
37+
{ "headers", (a, n) => { a.Headers = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
38+
{ "statusCode", (a, n) => { a.StatusCode = (HttpStatusCode)int.Parse(n.GetScalarValue()); } },
39+
};
40+
41+
public override void SerializeV2(IAsyncApiWriter writer)
2342
{
2443
if (writer is null)
2544
{
2645
throw new ArgumentNullException(nameof(writer));
2746
}
2847

2948
writer.WriteStartObject();
30-
3149
writer.WriteOptionalObject(AsyncApiConstants.Headers, this.Headers, (w, h) => h.SerializeV2(w));
32-
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
50+
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion ?? V2BindingVersion);
3351
writer.WriteExtensions(this.Extensions);
34-
3552
writer.WriteEndObject();
3653
}
3754

38-
public override string BindingKey => "http";
55+
public override void SerializeV3(IAsyncApiWriter writer)
56+
{
57+
if (writer is null)
58+
{
59+
throw new ArgumentNullException(nameof(writer));
60+
}
3961

40-
protected override FixedFieldMap<HttpMessageBinding> FixedFieldMap => new()
62+
writer.WriteStartObject();
63+
writer.WriteOptionalObject(AsyncApiConstants.Headers, this.Headers, (w, h) => h.SerializeV3(w));
64+
65+
if (this.StatusCode.HasValue)
66+
{
67+
writer.WriteRequiredProperty(AsyncApiConstants.StatusCode, (int)this.StatusCode.Value);
68+
}
69+
70+
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion ?? V3BindingVersion);
71+
writer.WriteExtensions(this.Extensions);
72+
writer.WriteEndObject();
73+
}
74+
75+
public override void SerializeProperties(IAsyncApiWriter writer)
4176
{
42-
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
43-
{ "headers", (a, n) => { a.Headers = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
44-
};
77+
this.SerializeV3(writer);
78+
}
4579
}
4680
}

src/ByteBard.AsyncAPI.Bindings/Http/HttpOperationBinding.cs

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,17 @@ namespace ByteBard.AsyncAPI.Bindings.Http
1010
/// <summary>
1111
/// Binding class for http operations.
1212
/// </summary>
13+
/// <remarks>
14+
/// The 'type' field exists in AsyncAPI V2 but is removed in V3 (inferred from operation action).
15+
/// </remarks>
1316
public class HttpOperationBinding : OperationBinding<HttpOperationBinding>
1417
{
18+
private const string V2BindingVersion = "0.2.0";
19+
private const string V3BindingVersion = "0.3.0";
20+
21+
/// <summary>
22+
/// Represents the HTTP operation type (used in V2 serialization).
23+
/// </summary>
1524
public enum HttpOperationType
1625
{
1726
[Display("request")]
@@ -22,12 +31,7 @@ public enum HttpOperationType
2231
}
2332

2433
/// <summary>
25-
/// REQUIRED. Type of operation. Its value MUST be either request or response.
26-
/// </summary>
27-
public HttpOperationType? Type { get; set; }
28-
29-
/// <summary>
30-
/// When type is request, this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, and TRACE.
34+
/// The HTTP method, e.g. GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, and TRACE.
3135
/// </summary>
3236
public string Method { get; set; }
3337

@@ -36,10 +40,16 @@ public enum HttpOperationType
3640
/// </summary>
3741
public AsyncApiJsonSchema Query { get; set; }
3842

39-
/// <summary>
40-
/// Serialize to AsyncAPI V2 document without using reference.
41-
/// </summary>
42-
public override void SerializeProperties(IAsyncApiWriter writer)
43+
public override string BindingKey => "http";
44+
45+
protected override FixedFieldMap<HttpOperationBinding> FixedFieldMap => new()
46+
{
47+
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
48+
{ "method", (a, n) => { a.Method = n.GetScalarValue(); } },
49+
{ "query", (a, n) => { a.Query = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
50+
};
51+
52+
public override void SerializeV2(IAsyncApiWriter writer)
4353
{
4454
if (writer is null)
4555
{
@@ -48,22 +58,50 @@ public override void SerializeProperties(IAsyncApiWriter writer)
4858

4959
writer.WriteStartObject();
5060

51-
writer.WriteRequiredProperty(AsyncApiConstants.Type, this.Type.GetDisplayName());
61+
var typeValue = this.InferTypeFromContext(writer);
62+
if (typeValue.HasValue)
63+
{
64+
writer.WriteRequiredProperty(AsyncApiConstants.Type, typeValue.GetDisplayName());
65+
}
66+
5267
writer.WriteOptionalProperty(AsyncApiConstants.Method, this.Method);
5368
writer.WriteOptionalObject(AsyncApiConstants.Query, this.Query, (w, h) => h.SerializeV2(w));
54-
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
69+
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion ?? V2BindingVersion);
5570
writer.WriteExtensions(this.Extensions);
5671
writer.WriteEndObject();
5772
}
5873

59-
protected override FixedFieldMap<HttpOperationBinding> FixedFieldMap => new()
74+
public override void SerializeV3(IAsyncApiWriter writer)
6075
{
61-
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
62-
{ "type", (a, n) => { a.Type = n.GetScalarValue().GetEnumFromDisplayName<HttpOperationType>(); } },
63-
{ "method", (a, n) => { a.Method = n.GetScalarValue(); } },
64-
{ "query", (a, n) => { a.Query = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
65-
};
76+
if (writer is null)
77+
{
78+
throw new ArgumentNullException(nameof(writer));
79+
}
6680

67-
public override string BindingKey => "http";
81+
writer.WriteStartObject();
82+
writer.WriteOptionalProperty(AsyncApiConstants.Method, this.Method);
83+
writer.WriteOptionalObject(AsyncApiConstants.Query, this.Query, (w, h) => h.SerializeV3(w));
84+
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion ?? V3BindingVersion);
85+
writer.WriteExtensions(this.Extensions);
86+
writer.WriteEndObject();
87+
}
88+
89+
public override void SerializeProperties(IAsyncApiWriter writer)
90+
{
91+
this.SerializeV3(writer);
92+
}
93+
94+
private HttpOperationType? InferTypeFromContext(IAsyncApiWriter writer)
95+
{
96+
var parentOperation = writer.Workspace?.GetSerializationContext<AsyncApiOperation>();
97+
if (parentOperation == null)
98+
{
99+
return null;
100+
}
101+
102+
return parentOperation.Action == AsyncApiAction.Send
103+
? HttpOperationType.Request
104+
: HttpOperationType.Response;
105+
}
68106
}
69107
}

src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaChannelBinding.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,16 @@ public class KafkaChannelBinding : ChannelBinding<KafkaChannelBinding>
5454
{ "confluent.value.subject.name.strategy", (a, n) => { a.ConfluentValueSubjectName = n.GetScalarValue(); } },
5555
};
5656

57-
/// <summary>
58-
/// Serialize to AsyncAPI V2 document without using reference.
59-
/// </summary>
57+
public override void SerializeV2(IAsyncApiWriter writer)
58+
{
59+
this.SerializeV3(writer);
60+
}
61+
62+
public override void SerializeV3(IAsyncApiWriter writer)
63+
{
64+
this.SerializeProperties(writer);
65+
}
66+
6067
public override void SerializeProperties(IAsyncApiWriter writer)
6168
{
6269
if (writer is null)

src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ public class KafkaMessageBinding : MessageBinding<KafkaMessageBinding>
3535
/// The version of this binding. If omitted, "latest" MUST be assumed.
3636
/// </summary>
3737

38+
public override void SerializeV2(IAsyncApiWriter writer)
39+
{
40+
this.SerializeV3(writer);
41+
}
42+
43+
public override void SerializeV3(IAsyncApiWriter writer)
44+
{
45+
this.SerializeProperties(writer);
46+
}
47+
3848
public override void SerializeProperties(IAsyncApiWriter writer)
3949
{
4050
if (writer is null)
@@ -54,12 +64,6 @@ public override void SerializeProperties(IAsyncApiWriter writer)
5464
writer.WriteEndObject();
5565
}
5666

57-
/// <summary>
58-
/// Serializes the v2.
59-
/// </summary>
60-
/// <param name="writer">The writer.</param>
61-
/// <exception cref="ArgumentNullException">writer.</exception>
62-
6367
public override string BindingKey => "kafka";
6468

6569
protected override FixedFieldMap<KafkaMessageBinding> FixedFieldMap => new()

src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ public class KafkaOperationBinding : OperationBinding<KafkaOperationBinding>
3030
{ "clientId", (a, n) => { a.ClientId = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
3131
};
3232

33-
/// <summary>
34-
/// Serialize to AsyncAPI V2 document without using reference.
35-
/// </summary>
33+
public override void SerializeV2(IAsyncApiWriter writer)
34+
{
35+
this.SerializeV3(writer);
36+
}
37+
38+
public override void SerializeV3(IAsyncApiWriter writer)
39+
{
40+
this.SerializeProperties(writer);
41+
}
42+
3643
public override void SerializeProperties(IAsyncApiWriter writer)
3744
{
3845
if (writer is null)

src/ByteBard.AsyncAPI.Bindings/Kafka/KafkaServerBinding.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,16 @@ public class KafkaServerBinding : ServerBinding<KafkaServerBinding>
2929
{ "schemaRegistryVendor", (a, n) => { a.SchemaRegistryVendor = n.GetScalarValue(); } },
3030
};
3131

32-
/// <summary>
33-
/// Serialize to AsyncAPI V2 document without using reference.
34-
/// </summary>
32+
public override void SerializeV2(IAsyncApiWriter writer)
33+
{
34+
this.SerializeV3(writer);
35+
}
36+
37+
public override void SerializeV3(IAsyncApiWriter writer)
38+
{
39+
this.SerializeProperties(writer);
40+
}
41+
3542
public override void SerializeProperties(IAsyncApiWriter writer)
3643
{
3744
if (writer is null)

src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ public class MQTTMessageBinding : MessageBinding<MQTTMessageBinding>
3333
/// </summary>
3434
public string ResponseTopic { get; set; }
3535

36+
public override void SerializeV2(IAsyncApiWriter writer)
37+
{
38+
this.SerializeV3(writer);
39+
}
40+
41+
public override void SerializeV3(IAsyncApiWriter writer)
42+
{
43+
this.SerializeProperties(writer);
44+
}
45+
3646
public override void SerializeProperties(IAsyncApiWriter writer)
3747
{
3848
if (writer is null)

0 commit comments

Comments
 (0)