Skip to main content

hiroz_protocol/format/
mod.rs

1//! Key expression format trait and implementations.
2//!
3//! This module provides the [`KeyExprFormatter`] trait and concrete implementations
4//! for different key expression formats.
5
6#[cfg(feature = "rmw-zenoh")]
7pub mod rmw_zenoh;
8
9#[cfg(feature = "ros2dds")]
10pub mod ros2dds;
11
12use alloc::string::String;
13use zenoh::{key_expr::KeyExpr, session::ZenohId, Result};
14
15use crate::{
16    entity::{EndpointEntity, Entity, LivelinessKE, NodeEntity, TopicKE},
17    qos::QosProfile,
18};
19
20/// Key expression format selector.
21///
22/// Determines which key expression format to use for ROS 2 <-> Zenoh mapping.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24#[non_exhaustive]
25pub enum KeyExprFormat {
26    /// rmw_zenoh_cpp compatible format (default).
27    ///
28    /// - Topic key expressions use `strip_slashes()` (preserve internal slashes)
29    /// - Liveliness tokens use mangling (replace `/` with `%`)
30    /// - Format: `<domain>/<topic>/<type>/<hash>`
31    #[default]
32    RmwZenoh,
33
34    /// zenoh-plugin-ros2dds compatible format.
35    ///
36    /// Different key expression structure for DDS bridge compatibility.
37    Ros2Dds,
38}
39
40#[allow(unused_variables)]
41impl KeyExprFormat {
42    /// Generate topic key expression for data publication/subscription.
43    pub fn topic_key_expr(&self, entity: &EndpointEntity) -> Result<TopicKE> {
44        match self {
45            KeyExprFormat::RmwZenoh => {
46                #[cfg(feature = "rmw-zenoh")]
47                {
48                    rmw_zenoh::RmwZenohFormatter::topic_key_expr(entity)
49                }
50                #[cfg(not(feature = "rmw-zenoh"))]
51                {
52                    Err(zenoh::Error::from("rmw-zenoh format not enabled"))
53                }
54            }
55            KeyExprFormat::Ros2Dds => {
56                #[cfg(feature = "ros2dds")]
57                {
58                    ros2dds::Ros2DdsFormatter::topic_key_expr(entity)
59                }
60                #[cfg(not(feature = "ros2dds"))]
61                {
62                    Err(zenoh::Error::from("ros2dds format not enabled"))
63                }
64            }
65        }
66    }
67
68    /// Generate liveliness token for endpoint entity discovery.
69    pub fn liveliness_key_expr(
70        &self,
71        entity: &EndpointEntity,
72        zid: &ZenohId,
73    ) -> Result<LivelinessKE> {
74        match self {
75            KeyExprFormat::RmwZenoh => {
76                #[cfg(feature = "rmw-zenoh")]
77                {
78                    rmw_zenoh::RmwZenohFormatter::liveliness_key_expr(entity, zid)
79                }
80                #[cfg(not(feature = "rmw-zenoh"))]
81                {
82                    Err(zenoh::Error::from("rmw-zenoh format not enabled"))
83                }
84            }
85            KeyExprFormat::Ros2Dds => {
86                #[cfg(feature = "ros2dds")]
87                {
88                    ros2dds::Ros2DdsFormatter::liveliness_key_expr(entity, zid)
89                }
90                #[cfg(not(feature = "ros2dds"))]
91                {
92                    Err(zenoh::Error::from("ros2dds format not enabled"))
93                }
94            }
95        }
96    }
97
98    /// Generate liveliness token for node entity discovery.
99    pub fn node_liveliness_key_expr(&self, entity: &NodeEntity) -> Result<LivelinessKE> {
100        match self {
101            KeyExprFormat::RmwZenoh => {
102                #[cfg(feature = "rmw-zenoh")]
103                {
104                    rmw_zenoh::RmwZenohFormatter::node_liveliness_key_expr(entity)
105                }
106                #[cfg(not(feature = "rmw-zenoh"))]
107                {
108                    Err(zenoh::Error::from("rmw-zenoh format not enabled"))
109                }
110            }
111            KeyExprFormat::Ros2Dds => {
112                #[cfg(feature = "ros2dds")]
113                {
114                    ros2dds::Ros2DdsFormatter::node_liveliness_key_expr(entity)
115                }
116                #[cfg(not(feature = "ros2dds"))]
117                {
118                    Err(zenoh::Error::from("ros2dds format not enabled"))
119                }
120            }
121        }
122    }
123
124    /// Parse liveliness token back to entity.
125    pub fn parse_liveliness(&self, ke: &KeyExpr) -> Result<Entity> {
126        match self {
127            KeyExprFormat::RmwZenoh => {
128                #[cfg(feature = "rmw-zenoh")]
129                {
130                    rmw_zenoh::RmwZenohFormatter::parse_liveliness(ke)
131                }
132                #[cfg(not(feature = "rmw-zenoh"))]
133                {
134                    Err(zenoh::Error::from("rmw-zenoh format not enabled"))
135                }
136            }
137            KeyExprFormat::Ros2Dds => {
138                #[cfg(feature = "ros2dds")]
139                {
140                    ros2dds::Ros2DdsFormatter::parse_liveliness(ke)
141                }
142                #[cfg(not(feature = "ros2dds"))]
143                {
144                    Err(zenoh::Error::from("ros2dds format not enabled"))
145                }
146            }
147        }
148    }
149
150    /// Encode QoS for liveliness token.
151    pub fn encode_qos(&self, qos: &QosProfile, keyless: bool) -> String {
152        match self {
153            KeyExprFormat::RmwZenoh => {
154                #[cfg(feature = "rmw-zenoh")]
155                {
156                    rmw_zenoh::RmwZenohFormatter::encode_qos(qos, keyless)
157                }
158                #[cfg(not(feature = "rmw-zenoh"))]
159                {
160                    String::new()
161                }
162            }
163            KeyExprFormat::Ros2Dds => {
164                #[cfg(feature = "ros2dds")]
165                {
166                    ros2dds::Ros2DdsFormatter::encode_qos(qos, keyless)
167                }
168                #[cfg(not(feature = "ros2dds"))]
169                {
170                    String::new()
171                }
172            }
173        }
174    }
175
176    /// Decode QoS from liveliness token.
177    pub fn decode_qos(&self, s: &str) -> Result<(bool, QosProfile)> {
178        match self {
179            KeyExprFormat::RmwZenoh => {
180                #[cfg(feature = "rmw-zenoh")]
181                {
182                    rmw_zenoh::RmwZenohFormatter::decode_qos(s)
183                }
184                #[cfg(not(feature = "rmw-zenoh"))]
185                {
186                    Err(zenoh::Error::from("rmw-zenoh format not enabled"))
187                }
188            }
189            KeyExprFormat::Ros2Dds => {
190                #[cfg(feature = "ros2dds")]
191                {
192                    ros2dds::Ros2DdsFormatter::decode_qos(s)
193                }
194                #[cfg(not(feature = "ros2dds"))]
195                {
196                    Err(zenoh::Error::from("ros2dds format not enabled"))
197                }
198            }
199        }
200    }
201}
202
203/// Trait for key expression format implementations.
204///
205/// This trait abstracts the differences between key expression formats
206/// used by different Zenoh-ROS bridges.
207pub trait KeyExprFormatter {
208    /// Escape character used to replace slashes in key expressions.
209    const ESCAPE_CHAR: char;
210
211    /// Admin space prefix for liveliness tokens.
212    const ADMIN_SPACE: &'static str;
213
214    /// Generate topic key expression for data publication/subscription.
215    fn topic_key_expr(entity: &EndpointEntity) -> Result<TopicKE>;
216
217    /// Generate liveliness token for endpoint entity discovery.
218    fn liveliness_key_expr(entity: &EndpointEntity, zid: &ZenohId) -> Result<LivelinessKE>;
219
220    /// Generate liveliness token for node entity discovery.
221    fn node_liveliness_key_expr(entity: &NodeEntity) -> Result<LivelinessKE>;
222
223    /// Parse liveliness token back to entity.
224    fn parse_liveliness(ke: &KeyExpr) -> Result<Entity>;
225
226    /// Mangle a name (replace slashes with escape char).
227    fn mangle_name(name: &str) -> String {
228        name.replace('/', &Self::ESCAPE_CHAR.to_string())
229    }
230
231    /// Demangle a name (restore slashes from escape char).
232    fn demangle_name(name: &str) -> String {
233        name.replace(Self::ESCAPE_CHAR, "/")
234    }
235
236    /// Encode QoS for liveliness token.
237    fn encode_qos(qos: &QosProfile, keyless: bool) -> String;
238
239    /// Decode QoS from liveliness token.
240    fn decode_qos(s: &str) -> Result<(bool, QosProfile)>;
241}