Skip to content

Commit fe389da

Browse files
authored
Merge pull request #53 from redboltz/add_publish_packet_id_optional
Add Option type packet_id() support for Publish packet builder.
2 parents 2a7c7e5 + eaa9036 commit fe389da

File tree

6 files changed

+300
-9
lines changed

6 files changed

+300
-9
lines changed

src/mqtt/packet/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub use self::variable_byte_integer::{DecodeResult, VariableByteInteger};
3838
mod packet_type;
3939
pub use self::packet_type::{FixedHeader, PacketType};
4040
mod packet_id;
41-
pub use self::packet_id::IsPacketId;
41+
pub use self::packet_id::{IntoPacketId, IsPacketId};
4242
pub mod v3_1_1;
4343
pub mod v5_0;
4444
pub use self::enum_packet::{GenericPacket, GenericPacketDisplay, GenericPacketTrait, Packet};

src/mqtt/packet/packet_id.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,70 @@ impl IsPacketId for u32 {
7070
}
7171
}
7272
}
73+
74+
/// Trait for types that can be converted into an optional packet ID
75+
///
76+
/// This trait enables the packet_id() builder method to accept both direct values
77+
/// (e.g., `packet_id(42)`) and optional values (e.g., `packet_id(Some(42))` or `packet_id(None)`).
78+
///
79+
/// # Examples
80+
///
81+
/// ```
82+
/// use mqtt_protocol_core::mqtt::packet::IntoPacketId;
83+
///
84+
/// // Direct value
85+
/// let id1: Option<u16> = 42u16.into_packet_id();
86+
/// assert_eq!(id1, Some(42));
87+
///
88+
/// // Optional value
89+
/// let id2: Option<u16> = Some(42u16).into_packet_id();
90+
/// assert_eq!(id2, Some(42));
91+
///
92+
/// // None value
93+
/// let id3: Option<u16> = None::<u16>.into_packet_id();
94+
/// assert_eq!(id3, None);
95+
/// ```
96+
pub trait IntoPacketId<T> {
97+
/// Convert self into an optional packet ID
98+
fn into_packet_id(self) -> Option<T>;
99+
}
100+
101+
// Implementations for u16
102+
103+
/// Implementation for direct u16 packet ID values
104+
///
105+
/// Allows direct u16 values like `42u16` to be converted to `Some(42)`
106+
impl IntoPacketId<u16> for u16 {
107+
fn into_packet_id(self) -> Option<u16> {
108+
Some(self)
109+
}
110+
}
111+
112+
/// Implementation for optional u16 packet ID values
113+
///
114+
/// Allows optional values like `Some(42u16)` or `None::<u16>` to be passed through
115+
impl IntoPacketId<u16> for Option<u16> {
116+
fn into_packet_id(self) -> Option<u16> {
117+
self
118+
}
119+
}
120+
121+
// Implementations for u32
122+
123+
/// Implementation for direct u32 packet ID values
124+
///
125+
/// Allows direct u32 values like `42u32` to be converted to `Some(42)`
126+
impl IntoPacketId<u32> for u32 {
127+
fn into_packet_id(self) -> Option<u32> {
128+
Some(self)
129+
}
130+
}
131+
132+
/// Implementation for optional u32 packet ID values
133+
///
134+
/// Allows optional values like `Some(42u32)` or `None::<u32>` to be passed through
135+
impl IntoPacketId<u32> for Option<u32> {
136+
fn into_packet_id(self) -> Option<u32> {
137+
self
138+
}
139+
}

src/mqtt/packet/v3_1_1/publish.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use crate::mqtt::packet::qos::Qos;
3939
use crate::mqtt::packet::variable_byte_integer::VariableByteInteger;
4040
use crate::mqtt::packet::GenericPacketDisplay;
4141
use crate::mqtt::packet::GenericPacketTrait;
42-
use crate::mqtt::packet::IsPacketId;
42+
use crate::mqtt::packet::{IntoPacketId, IsPacketId};
4343
use crate::mqtt::result_code::MqttError;
4444
use crate::mqtt::{Arc, ArcPayload, IntoPayload};
4545

@@ -843,9 +843,14 @@ where
843843
/// and must be a non-zero value. It is used to match the packet with its
844844
/// corresponding acknowledgment packets (PUBACK, PUBREC, etc.).
845845
///
846+
/// This method accepts both direct values and `Option<PacketIdType>`:
847+
/// - `packet_id(42)` - Sets packet ID to 42 (for QoS 1/2, backward compatible)
848+
/// - `packet_id(Some(42))` - Sets packet ID to 42 (for QoS 1/2)
849+
/// - `packet_id(None)` - No packet ID (for QoS 0)
850+
///
846851
/// # Parameters
847852
///
848-
/// * `id` - The packet identifier (must be non-zero for QoS > 0)
853+
/// * `id` - The packet identifier value or Option (must be non-zero for QoS > 0)
849854
///
850855
/// # Returns
851856
///
@@ -857,14 +862,32 @@ where
857862
/// use mqtt_protocol_core::mqtt;
858863
/// use mqtt_protocol_core::mqtt::packet::qos::Qos;
859864
///
865+
/// // Direct value (backward compatible)
860866
/// let builder = mqtt::packet::v3_1_1::Publish::builder()
861867
/// .topic_name("test/topic")
862868
/// .unwrap()
863869
/// .qos(Qos::AtLeastOnce)
864870
/// .packet_id(123);
871+
///
872+
/// // Explicit Some
873+
/// let builder = mqtt::packet::v3_1_1::Publish::builder()
874+
/// .topic_name("test/topic")
875+
/// .unwrap()
876+
/// .qos(Qos::AtLeastOnce)
877+
/// .packet_id(Some(123));
878+
///
879+
/// // Explicit None for QoS 0
880+
/// let builder = mqtt::packet::v3_1_1::Publish::builder()
881+
/// .topic_name("test/topic")
882+
/// .unwrap()
883+
/// .qos(Qos::AtMostOnce)
884+
/// .packet_id(None);
865885
/// ```
866-
pub fn packet_id(mut self, id: PacketIdType) -> Self {
867-
self.packet_id_buf = Some(Some(id.to_buffer()));
886+
pub fn packet_id<T>(mut self, id: T) -> Self
887+
where
888+
T: IntoPacketId<PacketIdType>,
889+
{
890+
self.packet_id_buf = Some(id.into_packet_id().map(|i| i.to_buffer()));
868891
self
869892
}
870893

src/mqtt/packet/v5_0/publish.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ use crate::mqtt::packet::topic_alias_send::TopicAliasType;
4242
use crate::mqtt::packet::variable_byte_integer::VariableByteInteger;
4343
use crate::mqtt::packet::GenericPacketDisplay;
4444
use crate::mqtt::packet::GenericPacketTrait;
45-
use crate::mqtt::packet::IsPacketId;
4645
#[cfg(feature = "std")]
4746
use crate::mqtt::packet::PropertiesToBuffers;
47+
use crate::mqtt::packet::{IntoPacketId, IsPacketId};
4848
use crate::mqtt::packet::{Properties, PropertiesParse, PropertiesSize, Property};
4949
use crate::mqtt::result_code::MqttError;
5050
use crate::mqtt::{Arc, ArcPayload, IntoPayload};
@@ -1141,9 +1141,14 @@ where
11411141
/// to match acknowledgment packets (PUBACK, PUBREC, etc.) with the original
11421142
/// PUBLISH packet.
11431143
///
1144+
/// This method accepts both direct values and `Option<PacketIdType>`:
1145+
/// - `packet_id(42)` - Sets packet ID to 42 (for QoS 1/2, backward compatible)
1146+
/// - `packet_id(Some(42))` - Sets packet ID to 42 (for QoS 1/2)
1147+
/// - `packet_id(None)` - No packet ID (for QoS 0)
1148+
///
11441149
/// # Parameters
11451150
///
1146-
/// - `id`: The packet identifier (must be non-zero for QoS > 0)
1151+
/// - `id`: The packet identifier value or Option (must be non-zero for QoS > 0)
11471152
///
11481153
/// # Returns
11491154
///
@@ -1154,11 +1159,23 @@ where
11541159
/// ```ignore
11551160
/// use mqtt_protocol_core::mqtt;
11561161
///
1162+
/// // Direct value (backward compatible)
11571163
/// let builder = mqtt::packet::v5_0::Publish::builder()
11581164
/// .packet_id(42);
1165+
///
1166+
/// // Explicit Some
1167+
/// let builder = mqtt::packet::v5_0::Publish::builder()
1168+
/// .packet_id(Some(42));
1169+
///
1170+
/// // Explicit None for QoS 0
1171+
/// let builder = mqtt::packet::v5_0::Publish::builder()
1172+
/// .packet_id(None);
11591173
/// ```
1160-
pub fn packet_id(mut self, id: PacketIdType) -> Self {
1161-
self.packet_id_buf = Some(Some(id.to_buffer()));
1174+
pub fn packet_id<T>(mut self, id: T) -> Self
1175+
where
1176+
T: IntoPacketId<PacketIdType>,
1177+
{
1178+
self.packet_id_buf = Some(id.into_packet_id().map(|i| i.to_buffer()));
11621179
self
11631180
}
11641181

tests/packet-v3_1_1-publish.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,95 @@ fn test_packet_type() {
668668
let packet_type = mqtt::packet::v3_1_1::Publish::packet_type();
669669
assert_eq!(packet_type, mqtt::packet::PacketType::Publish);
670670
}
671+
672+
// Tests for packet_id() with Option interface
673+
674+
#[test]
675+
fn test_qos1_with_some_packet_id_success() {
676+
common::init_tracing();
677+
let result = mqtt::packet::v3_1_1::Publish::builder()
678+
.topic_name("test/topic")
679+
.unwrap()
680+
.qos(mqtt::packet::Qos::AtLeastOnce)
681+
.packet_id(Some(42u16))
682+
.build()
683+
.unwrap();
684+
assert_eq!(result.packet_id(), Some(42u16));
685+
}
686+
687+
#[test]
688+
fn test_qos2_with_some_packet_id_success() {
689+
common::init_tracing();
690+
let result = mqtt::packet::v3_1_1::Publish::builder()
691+
.topic_name("test/topic")
692+
.unwrap()
693+
.qos(mqtt::packet::Qos::ExactlyOnce)
694+
.packet_id(Some(123u16))
695+
.build()
696+
.unwrap();
697+
assert_eq!(result.packet_id(), Some(123u16));
698+
}
699+
700+
#[test]
701+
fn test_qos1_with_none_packet_id_error() {
702+
common::init_tracing();
703+
let err = mqtt::packet::v3_1_1::Publish::builder()
704+
.topic_name("test/topic")
705+
.unwrap()
706+
.qos(mqtt::packet::Qos::AtLeastOnce)
707+
.packet_id(None::<u16>)
708+
.build()
709+
.unwrap_err();
710+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
711+
}
712+
713+
#[test]
714+
fn test_qos2_with_none_packet_id_error() {
715+
common::init_tracing();
716+
let err = mqtt::packet::v3_1_1::Publish::builder()
717+
.topic_name("test/topic")
718+
.unwrap()
719+
.qos(mqtt::packet::Qos::ExactlyOnce)
720+
.packet_id(None::<u16>)
721+
.build()
722+
.unwrap_err();
723+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
724+
}
725+
726+
#[test]
727+
fn test_qos0_with_none_packet_id_success() {
728+
common::init_tracing();
729+
let result = mqtt::packet::v3_1_1::Publish::builder()
730+
.topic_name("test/topic")
731+
.unwrap()
732+
.qos(mqtt::packet::Qos::AtMostOnce)
733+
.packet_id(None::<u16>)
734+
.build()
735+
.unwrap();
736+
assert_eq!(result.packet_id(), None);
737+
}
738+
739+
#[test]
740+
fn test_qos0_with_some_packet_id_error() {
741+
common::init_tracing();
742+
let err = mqtt::packet::v3_1_1::Publish::builder()
743+
.topic_name("test/topic")
744+
.unwrap()
745+
.qos(mqtt::packet::Qos::AtMostOnce)
746+
.packet_id(Some(42u16))
747+
.build()
748+
.unwrap_err();
749+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
750+
}
751+
752+
#[test]
753+
fn test_qos0_without_packet_id_success() {
754+
common::init_tracing();
755+
let result = mqtt::packet::v3_1_1::Publish::builder()
756+
.topic_name("test/topic")
757+
.unwrap()
758+
.qos(mqtt::packet::Qos::AtMostOnce)
759+
.build()
760+
.unwrap();
761+
assert_eq!(result.packet_id(), None);
762+
}

tests/packet-v5_0-publish.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,3 +1504,95 @@ fn test_packet_type() {
15041504
let packet_type = mqtt::packet::v5_0::Publish::packet_type();
15051505
assert_eq!(packet_type, mqtt::packet::PacketType::Publish);
15061506
}
1507+
1508+
// Tests for packet_id() with Option interface
1509+
1510+
#[test]
1511+
fn test_qos1_with_some_packet_id_success() {
1512+
common::init_tracing();
1513+
let result = mqtt::packet::v5_0::Publish::builder()
1514+
.topic_name("test/topic")
1515+
.unwrap()
1516+
.qos(mqtt::packet::Qos::AtLeastOnce)
1517+
.packet_id(Some(42u16))
1518+
.build()
1519+
.unwrap();
1520+
assert_eq!(result.packet_id(), Some(42u16));
1521+
}
1522+
1523+
#[test]
1524+
fn test_qos2_with_some_packet_id_success() {
1525+
common::init_tracing();
1526+
let result = mqtt::packet::v5_0::Publish::builder()
1527+
.topic_name("test/topic")
1528+
.unwrap()
1529+
.qos(mqtt::packet::Qos::ExactlyOnce)
1530+
.packet_id(Some(123u16))
1531+
.build()
1532+
.unwrap();
1533+
assert_eq!(result.packet_id(), Some(123u16));
1534+
}
1535+
1536+
#[test]
1537+
fn test_qos1_with_none_packet_id_error() {
1538+
common::init_tracing();
1539+
let err = mqtt::packet::v5_0::Publish::builder()
1540+
.topic_name("test/topic")
1541+
.unwrap()
1542+
.qos(mqtt::packet::Qos::AtLeastOnce)
1543+
.packet_id(None::<u16>)
1544+
.build()
1545+
.unwrap_err();
1546+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
1547+
}
1548+
1549+
#[test]
1550+
fn test_qos2_with_none_packet_id_error() {
1551+
common::init_tracing();
1552+
let err = mqtt::packet::v5_0::Publish::builder()
1553+
.topic_name("test/topic")
1554+
.unwrap()
1555+
.qos(mqtt::packet::Qos::ExactlyOnce)
1556+
.packet_id(None::<u16>)
1557+
.build()
1558+
.unwrap_err();
1559+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
1560+
}
1561+
1562+
#[test]
1563+
fn test_qos0_with_none_packet_id_success() {
1564+
common::init_tracing();
1565+
let result = mqtt::packet::v5_0::Publish::builder()
1566+
.topic_name("test/topic")
1567+
.unwrap()
1568+
.qos(mqtt::packet::Qos::AtMostOnce)
1569+
.packet_id(None::<u16>)
1570+
.build()
1571+
.unwrap();
1572+
assert_eq!(result.packet_id(), None);
1573+
}
1574+
1575+
#[test]
1576+
fn test_qos0_with_some_packet_id_error() {
1577+
common::init_tracing();
1578+
let err = mqtt::packet::v5_0::Publish::builder()
1579+
.topic_name("test/topic")
1580+
.unwrap()
1581+
.qos(mqtt::packet::Qos::AtMostOnce)
1582+
.packet_id(Some(42u16))
1583+
.build()
1584+
.unwrap_err();
1585+
assert_eq!(err, mqtt::result_code::MqttError::MalformedPacket);
1586+
}
1587+
1588+
#[test]
1589+
fn test_qos0_without_packet_id_success() {
1590+
common::init_tracing();
1591+
let result = mqtt::packet::v5_0::Publish::builder()
1592+
.topic_name("test/topic")
1593+
.unwrap()
1594+
.qos(mqtt::packet::Qos::AtMostOnce)
1595+
.build()
1596+
.unwrap();
1597+
assert_eq!(result.packet_id(), None);
1598+
}

0 commit comments

Comments
 (0)