Skip to content

Commit ccdb44e

Browse files
bieluclaude
andcommitted
refactor: model MQTT scalar-or-schema values as a type hierarchy
MQTTIntegerOrSchema and MQTTStringOrSchema are now abstract bases with MQTTInteger/MQTTIntegerSchema and MQTTString/MQTTStringSchema subtypes, so callers use type checks instead of null checks. Implicit conversions from long/string/AsyncApiJsonSchema keep assignments such as SessionExpiryInterval = 600 compiling, and each subtype converts back to its underlying value. The lossy int?/string accessors and the *Value properties are removed; SessionExpiryInterval, MaximumPacketSize, MessageExpiryInterval and ResponseTopic now expose the union type directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a488b73 commit ccdb44e

8 files changed

Lines changed: 168 additions & 166 deletions

File tree

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,9 @@ public class MQTTMessageBinding : MessageBinding<MQTTMessageBinding>
3030

3131
/// <summary>
3232
/// The topic (channel URI) for a response message.
33+
/// Either an <see cref="MQTTString"/> or an <see cref="MQTTStringSchema"/>.
3334
/// </summary>
34-
public string ResponseTopic
35-
{
36-
get => this.ResponseTopicValue?.Value;
37-
set => this.ResponseTopicValue = value == null ? null : MQTTStringOrSchema.FromString(value);
38-
}
39-
40-
/// <summary>The string-or-schema response topic value.</summary>
41-
public MQTTStringOrSchema ResponseTopicValue { get; set; }
35+
public MQTTStringOrSchema ResponseTopic { get; set; }
4236

4337
/// <summary>
4438
/// Writes the MQTT message binding object and its extensions for AsyncAPI 2.0.
@@ -61,7 +55,7 @@ public override void SerializeV3(IAsyncApiWriter writer)
6155
/// <summary>
6256
/// Writes the MQTT message binding object and its extensions for AsyncAPI 3.0.
6357
/// </summary>
64-
/// <remarks>ResponseTopicValue is written as a string or schema; nested schemas use AsyncAPI 3.0 serialization.</remarks>
58+
/// <remarks>ResponseTopic is written as a string or schema; nested schemas use AsyncAPI 3.0 serialization.</remarks>
6559
/// <exception cref="ArgumentNullException"><paramref name="writer"/> is null.</exception>
6660
public override void SerializeProperties(IAsyncApiWriter writer)
6761
{
@@ -85,8 +79,12 @@ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version
8579
writer.WriteOptionalProperty("payloadFormatIndicator", this.PayloadFormatIndicator);
8680
writer.WriteOptionalObject("correlationData", this.CorrelationData, (w, h) => { if (version == AsyncApiVersion.AsyncApi2_0) h.SerializeV2(w); else h.SerializeV3(w); });
8781
writer.WriteOptionalProperty("contentType", this.ContentType);
88-
if (this.ResponseTopicValue?.Value != null) writer.WriteOptionalProperty("responseTopic", this.ResponseTopicValue.Value);
89-
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); });
82+
if (this.ResponseTopic != null)
83+
{
84+
writer.WritePropertyName("responseTopic");
85+
this.ResponseTopic.Serialize(writer, version);
86+
}
87+
9088
writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
9189
writer.WriteExtensions(this.Extensions);
9290
writer.WriteEndObject();
@@ -100,7 +98,7 @@ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version
10098
{ "payloadFormatIndicator", (a, n) => { a.PayloadFormatIndicator = n.GetIntegerValue(); } },
10199
{ "correlationData", (a, n) => { a.CorrelationData = AsyncApiJsonSchemaDeserializer.LoadSchema(n); } },
102100
{ "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } },
103-
{ "responseTopic", (a, n) => { a.ResponseTopicValue = MQTTStringOrSchema.Parse(n); } },
101+
{ "responseTopic", (a, n) => { a.ResponseTopic = MQTTStringOrSchema.Parse(n); } },
104102
};
105103
}
106104
}

‎src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs‎

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,9 @@ public bool Retain
3434

3535
/// <summary>
3636
/// Interval in seconds or a Schema Object containing the definition of the lifetime of the message.
37+
/// Either an <see cref="MQTTInteger"/> or an <see cref="MQTTIntegerSchema"/>.
3738
/// </summary>
38-
/// <remarks>
39-
/// Returns null when the value is a schema or does not fit in an Int32 (MQTT allows up to 4294967295).
40-
/// Use <see cref="MessageExpiryIntervalValue"/> to read every value.
41-
/// </remarks>
42-
[Obsolete("Returns null for schema values and values above Int32.MaxValue. Use MessageExpiryIntervalValue instead.")]
43-
public int? MessageExpiryInterval
44-
{
45-
get => this.MessageExpiryIntervalValue?.Integer;
46-
set => this.MessageExpiryIntervalValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
47-
}
48-
49-
/// <summary>The integer-or-schema message expiry interval value.</summary>
50-
public MQTTIntegerOrSchema MessageExpiryIntervalValue { get; set; }
39+
public MQTTIntegerOrSchema MessageExpiryInterval { get; set; }
5140

5241
public override string BindingKey => "mqtt";
5342

@@ -56,7 +45,7 @@ public int? MessageExpiryInterval
5645
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
5746
{ "qos", (a, n) => { a.QoS = n.GetIntegerValue(); } },
5847
{ "retain", (a, n) => { a.Retain = n.GetBooleanValue(); } },
59-
{ "messageExpiryInterval", (a, n) => { a.MessageExpiryIntervalValue = MQTTIntegerOrSchema.Parse(n); } },
48+
{ "messageExpiryInterval", (a, n) => { a.MessageExpiryInterval = MQTTIntegerOrSchema.Parse(n); } },
6049
};
6150

6251
/// <summary>
@@ -103,12 +92,12 @@ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version
10392
writer.WriteStartObject();
10493
writer.WriteOptionalProperty("qos", this.qos);
10594
writer.WriteOptionalProperty("retain", this.retain);
106-
if (this.MessageExpiryIntervalValue?.LongInteger is long integer) writer.WriteRequiredProperty("messageExpiryInterval", integer);
107-
else if (this.MessageExpiryIntervalValue?.Schema != null) writer.WriteOptionalObject("messageExpiryInterval", this.MessageExpiryIntervalValue.Schema, (w, schema) =>
95+
if (this.MessageExpiryInterval != null)
10896
{
109-
if (version == AsyncApiVersion.AsyncApi2_0) schema.SerializeV2(w);
110-
else schema.SerializeV3(w);
111-
});
97+
writer.WritePropertyName("messageExpiryInterval");
98+
this.MessageExpiryInterval.Serialize(writer, version);
99+
}
100+
112101
writer.WriteOptionalProperty("bindingVersion", this.BindingVersion);
113102
writer.WriteExtensions(this.Extensions);
114103
writer.WriteEndObject();

‎src/ByteBard.AsyncAPI.Bindings/MQTT/MQTTServerBinding.cs‎

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,16 @@ public class MQTTServerBinding : ServerBinding<MQTTServerBinding>
3636
/// <summary>
3737
/// Interval in seconds the broker maintains a session
3838
/// for a disconnected client until this interval expires.
39+
/// Either an <see cref="MQTTInteger"/> or an <see cref="MQTTIntegerSchema"/>.
3940
/// </summary>
40-
/// <remarks>
41-
/// Returns null when the value is a schema or does not fit in an Int32 (MQTT allows up to 4294967295).
42-
/// Use <see cref="SessionExpiryIntervalValue"/> to read every value.
43-
/// </remarks>
44-
[Obsolete("Returns null for schema values and values above Int32.MaxValue. Use SessionExpiryIntervalValue instead.")]
45-
public int? SessionExpiryInterval
46-
{
47-
get => this.SessionExpiryIntervalValue?.Integer;
48-
set => this.SessionExpiryIntervalValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
49-
}
50-
51-
/// <summary>The integer-or-schema session expiry interval value.</summary>
52-
public MQTTIntegerOrSchema SessionExpiryIntervalValue { get; set; }
41+
public MQTTIntegerOrSchema SessionExpiryInterval { get; set; }
5342

5443
/// <summary>
5544
/// Number of bytes representing the maximum packet size
5645
/// the client is willing to accept.
46+
/// Either an <see cref="MQTTInteger"/> or an <see cref="MQTTIntegerSchema"/>.
5747
/// </summary>
58-
/// <remarks>
59-
/// Returns null when the value is a schema or does not fit in an Int32 (MQTT allows up to 4294967295).
60-
/// Use <see cref="MaximumPacketSizeValue"/> to read every value.
61-
/// </remarks>
62-
[Obsolete("Returns null for schema values and values above Int32.MaxValue. Use MaximumPacketSizeValue instead.")]
63-
public int? MaximumPacketSize
64-
{
65-
get => this.MaximumPacketSizeValue?.Integer;
66-
set => this.MaximumPacketSizeValue = value.HasValue ? MQTTIntegerOrSchema.FromInteger(value.Value) : null;
67-
}
68-
69-
/// <summary>The integer-or-schema maximum packet size value.</summary>
70-
public MQTTIntegerOrSchema MaximumPacketSizeValue { get; set; }
48+
public MQTTIntegerOrSchema MaximumPacketSize { get; set; }
7149

7250
public override string BindingKey => "mqtt";
7351

@@ -78,8 +56,8 @@ public int? MaximumPacketSize
7856
{ "cleanSession", (a, n) => { a.CleanSession = n.GetBooleanValue(); } },
7957
{ "lastWill", (a, n) => { a.LastWill = n.ParseMap(LastWillFixedFields); } },
8058
{ "keepAlive", (a, n) => { a.KeepAlive = n.GetIntegerValue(); } },
81-
{ "sessionExpiryInterval", (a, n) => { a.SessionExpiryIntervalValue = MQTTIntegerOrSchema.Parse(n); } },
82-
{ "maximumPacketSize", (a, n) => { a.MaximumPacketSizeValue = MQTTIntegerOrSchema.Parse(n); } },
59+
{ "sessionExpiryInterval", (a, n) => { a.SessionExpiryInterval = MQTTIntegerOrSchema.Parse(n); } },
60+
{ "maximumPacketSize", (a, n) => { a.MaximumPacketSize = MQTTIntegerOrSchema.Parse(n); } },
8361
};
8462

8563
private static FixedFieldMap<LastWill> LastWillFixedFields = new()
@@ -136,8 +114,8 @@ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version
136114
writer.WriteOptionalProperty("cleanSession", this.CleanSession);
137115
writer.WriteOptionalObject("lastWill", this.LastWill, (w, l) => l.Serialize(w));
138116
writer.WriteOptionalProperty("keepAlive", this.KeepAlive);
139-
this.WriteIntegerOrSchema(writer, "sessionExpiryInterval", this.SessionExpiryIntervalValue, version);
140-
this.WriteIntegerOrSchema(writer, "maximumPacketSize", this.MaximumPacketSizeValue, version);
117+
this.WriteIntegerOrSchema(writer, "sessionExpiryInterval", this.SessionExpiryInterval, version);
118+
this.WriteIntegerOrSchema(writer, "maximumPacketSize", this.MaximumPacketSize, version);
141119
writer.WriteOptionalProperty(AsyncApiConstants.BindingVersion, this.BindingVersion);
142120
writer.WriteExtensions(this.Extensions);
143121
writer.WriteEndObject();
@@ -153,12 +131,13 @@ private void SerializeProperties(IAsyncApiWriter writer, AsyncApiVersion version
153131
/// <exception cref="ArgumentNullException">A value is present and <paramref name="writer"/> or <paramref name="name"/> is null.</exception>
154132
private void WriteIntegerOrSchema(IAsyncApiWriter writer, string name, MQTTIntegerOrSchema value, AsyncApiVersion version)
155133
{
156-
if (value?.LongInteger is long integer) writer.WriteRequiredProperty(name, integer);
157-
else if (value?.Schema != null) writer.WriteOptionalObject(name, value.Schema, (w, schema) =>
134+
if (value == null)
158135
{
159-
if (version == AsyncApiVersion.AsyncApi2_0) schema.SerializeV2(w);
160-
else schema.SerializeV3(w);
161-
});
136+
return;
137+
}
138+
139+
writer.WritePropertyName(name);
140+
value.Serialize(writer, version);
162141
}
163142
}
164143
}

0 commit comments

Comments
 (0)