Skip to content

Commit 150d6d7

Browse files
bieluclaude
authored andcommitted
fix: stop adding topic and retain when writing an MQTT lastWill
LastWill wrote `topic` with WriteRequiredProperty and `retain` from a non-nullable bool, so reading `lastWill: { qos: 1 }` and writing it back produced `topic:` (null) and `retain: false`. Both fields are optional in the MQTT binding spec. `retain` now has a nullable backing field (same pattern as MQTTOperationBinding), and both fields are written only when set. The public `Retain` property keeps its bool type, so this is not a breaking API change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent da8a261 commit 150d6d7

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
public class LastWill : IAsyncApiElement
88
{
9+
private bool? retain;
10+
911
/// <summary>
1012
/// The topic where the Last Will and Testament message will be sent.
1113
/// </summary>
@@ -26,7 +28,11 @@ public class LastWill : IAsyncApiElement
2628
/// <summary>
2729
/// Whether the broker should retain the Last Will and Testament message or not.
2830
/// </summary>
29-
public bool Retain { get; set; }
31+
public bool Retain
32+
{
33+
get => this.retain ?? false;
34+
set => this.retain = value;
35+
}
3036

3137
public void Serialize(IAsyncApiWriter writer)
3238
{
@@ -36,10 +42,10 @@ public void Serialize(IAsyncApiWriter writer)
3642
}
3743

3844
writer.WriteStartObject();
39-
writer.WriteRequiredProperty("topic", this.Topic);
45+
writer.WriteOptionalProperty("topic", this.Topic);
4046
writer.WriteOptionalProperty("qos", (int?)this.QoS);
4147
writer.WriteOptionalProperty("message", this.Message);
42-
writer.WriteRequiredProperty("retain", this.Retain);
48+
writer.WriteOptionalProperty("retain", this.retain);
4349
writer.WriteEndObject();
4450
}
4551
}

‎test/ByteBard.AsyncAPI.Tests/Bindings/MQTT/MQTTBindingVariants_Should.cs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,35 @@ public void OperationBinding_OmitsUnsetQosAndRetain()
111111
var yaml = new MQTTOperationBinding().SerializeAsYaml(AsyncApiVersion.AsyncApi3_0);
112112
yaml.Should().NotContain("qos:").And.NotContain("retain:");
113113
}
114+
115+
[TestCase(AsyncApiVersion.AsyncApi2_0)]
116+
[TestCase(AsyncApiVersion.AsyncApi3_0)]
117+
public void LastWill_WithoutTopicAndRetain_RoundTripsWithoutAddingThem(AsyncApiVersion version)
118+
{
119+
var settings = new AsyncApiReaderSettings { Bindings = BindingsCollection.MQTT };
120+
var yaml = (version == AsyncApiVersion.AsyncApi2_0 ? "url" : "host") + ": localhost\n" +
121+
"""
122+
protocol: mqtt
123+
bindings:
124+
mqtt:
125+
lastWill:
126+
qos: 1
127+
""";
128+
129+
var server = new AsyncApiStringReader(settings).ReadFragment<AsyncApiServer>(yaml, version, out var diagnostics);
130+
diagnostics.Errors.Should().BeEmpty();
131+
132+
var actual = server.SerializeAsYaml(version);
133+
actual.Should().Contain("qos: 1").And.NotContain("topic:").And.NotContain("retain:");
134+
}
135+
136+
[Test]
137+
public void LastWill_WithExplicitRetainFalse_WritesRetain()
138+
{
139+
var lastWill = new LastWill { Topic = "/last-wills", Retain = false };
140+
var server = new MQTTServerBinding { LastWill = lastWill };
141+
142+
server.SerializeAsYaml(AsyncApiVersion.AsyncApi3_0).Should().Contain("topic: /last-wills").And.Contain("retain: false");
143+
}
114144
}
115145
}

0 commit comments

Comments
 (0)