1use alloc::string::{String, ToString};
15use core::{fmt::Display, str::FromStr};
16
17use serde::{de, Deserialize, Serialize};
18
19use crate::core::WhatAmI;
20
21#[repr(u8)]
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub enum Bound {
25 #[default]
26 North,
27 South,
28}
29
30impl Bound {
31 pub fn is_north(&self) -> bool {
32 *self == Bound::North
33 }
34
35 pub fn is_south(&self) -> bool {
36 *self == Bound::South
37 }
38}
39
40impl Display for Bound {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match self {
43 Bound::North => f.write_str("north"),
44 Bound::South => f.write_str("south"),
45 }
46 }
47}
48
49impl TryFrom<u8> for Bound {
50 type Error = InvalidBoundError;
51
52 fn try_from(value: u8) -> Result<Self, Self::Error> {
53 match value {
54 v if v == Bound::North as u8 => Ok(Bound::North),
55 v if v == Bound::South as u8 => Ok(Bound::South),
56 _ => Err(InvalidBoundError),
57 }
58 }
59}
60
61#[derive(Debug)]
62pub struct InvalidBoundError;
63
64impl Display for InvalidBoundError {
65 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66 write!(
67 f,
68 "a u8-encoded bound should either be {} (for '{}') or {} (for '{}')",
69 Bound::North as u8,
70 Bound::North,
71 Bound::South as u8,
72 Bound::South
73 )
74 }
75}
76
77#[cfg(feature = "std")]
78impl std::error::Error for InvalidBoundError {}
79
80#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub enum Region {
83 #[default]
85 North,
86 Local,
88 South { id: usize, mode: WhatAmI },
90}
91
92impl Region {
93 pub const fn default_south(mode: WhatAmI) -> Self {
94 Self::South { id: 0, mode }
95 }
96
97 pub fn bound(&self) -> Bound {
98 match self {
99 Region::North => Bound::North,
100 Region::South { .. } | Region::Local => Bound::South,
101 }
102 }
103
104 pub fn mode(&self) -> Option<WhatAmI> {
105 match self {
106 Region::North => None,
107 Region::Local => Some(WhatAmI::Client),
108 Region::South { mode, .. } => Some(*mode),
109 }
110 }
111}
112
113impl FromStr for Region {
114 type Err = InvalidRegionIdError;
115
116 fn from_str(s: &str) -> Result<Self, Self::Err> {
117 match s {
118 "north" => Ok(Region::North),
119 "local" => Ok(Region::Local),
120 _ => {
121 let mut substrings = s.splitn(3, ":");
122
123 let Some("south") = substrings.next() else {
124 return Err(InvalidRegionIdError::ExpectedSouth);
125 };
126
127 let number_str = substrings
128 .next()
129 .ok_or(InvalidRegionIdError::ExpectedNumber)?;
130
131 let number = number_str
132 .parse()
133 .map_err(InvalidRegionIdError::BadNumber)?;
134
135 let mode_str = substrings
136 .next()
137 .ok_or(InvalidRegionIdError::ExpectedWhatAmI)?;
138
139 let mode = mode_str.parse().map_err(InvalidRegionIdError::BadWhatAmI)?;
140
141 debug_assert!(substrings.next().is_none());
142
143 Ok(Region::South { id: number, mode })
144 }
145 }
146 }
147}
148
149#[derive(Debug)]
150pub enum InvalidRegionIdError {
151 ExpectedSouth,
152 ExpectedNumber,
153 ExpectedWhatAmI,
154 ExpectedEof,
155 BadNumber(core::num::ParseIntError),
156 BadWhatAmI(zenoh_result::ZError),
157}
158
159impl Display for InvalidRegionIdError {
160 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161 match self {
162 InvalidRegionIdError::ExpectedSouth => f.write_str("expected 'south' literal"),
163 InvalidRegionIdError::ExpectedNumber => f.write_str("expected u16 number"),
164 InvalidRegionIdError::ExpectedWhatAmI => f.write_str("expected mode"),
165 InvalidRegionIdError::ExpectedEof => f.write_str("expected EOF"),
166 InvalidRegionIdError::BadNumber(err) => write!(f, "error parsing number: {err}"),
167 InvalidRegionIdError::BadWhatAmI(err) => write!(f, "error parsing mode: {err}"),
168 }
169 }
170}
171
172impl Display for Region {
173 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174 match self {
175 Region::North => f.write_str("north"),
176 Region::Local => f.write_str("local"),
177 Region::South { id, mode } => write!(f, "south:{id}:{mode}"),
178 }
179 }
180}
181
182impl Serialize for Region {
183 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
184 where
185 S: serde::Serializer,
186 {
187 serializer.collect_str(self)
188 }
189}
190
191impl<'de> Deserialize<'de> for Region {
192 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193 where
194 D: serde::Deserializer<'de>,
195 {
196 String::deserialize(deserializer)?
197 .parse()
198 .map_err(de::Error::custom)
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct RegionName(String);
209
210impl RegionName {
211 pub const MAX_LEN: usize = 32;
212
213 pub fn as_str(&self) -> &str {
214 &self.0
215 }
216
217 pub fn into_string(self) -> String {
218 self.0
219 }
220
221 fn validate<S>(s: S) -> Result<S, InvalidRegionNameError>
222 where
223 S: AsRef<str>,
224 {
225 if s.as_ref().is_empty() {
226 return Err(InvalidRegionNameError::Empty);
227 }
228
229 if s.as_ref().len() > Self::MAX_LEN {
230 return Err(InvalidRegionNameError::TooLong);
231 }
232
233 Ok(s)
234 }
235
236 #[cfg(feature = "test")]
237 #[doc(hidden)]
238 pub fn rand() -> Self {
239 use rand::distributions::{Alphanumeric, DistString};
240
241 Alphanumeric
242 .sample_string(&mut rand::thread_rng(), Self::MAX_LEN)
243 .try_into()
244 .unwrap()
245 }
246}
247
248impl FromStr for RegionName {
249 type Err = InvalidRegionNameError;
250
251 fn from_str(s: &str) -> Result<Self, Self::Err> {
252 Self::validate(s).map(|s| Self(s.to_string()))
253 }
254}
255
256impl TryFrom<String> for RegionName {
257 type Error = InvalidRegionNameError;
258
259 fn try_from(s: String) -> Result<Self, Self::Error> {
260 Self::validate(s).map(Self)
261 }
262}
263
264#[derive(Debug)]
265pub enum InvalidRegionNameError {
266 Empty,
267 TooLong,
268}
269
270impl Display for InvalidRegionNameError {
271 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272 match self {
273 InvalidRegionNameError::Empty => f.write_str("region names should be non-empty"),
274 InvalidRegionNameError::TooLong => write!(
275 f,
276 "region names should be at most {} bytes",
277 RegionName::MAX_LEN
278 ),
279 }
280 }
281}
282
283#[cfg(feature = "std")]
284impl std::error::Error for InvalidRegionNameError {}
285
286impl Serialize for RegionName {
287 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
288 where
289 S: serde::Serializer,
290 {
291 serializer.serialize_str(self.as_str())
292 }
293}
294
295impl<'de> Deserialize<'de> for RegionName {
296 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297 where
298 D: serde::Deserializer<'de>,
299 {
300 String::deserialize(deserializer)?
301 .parse()
302 .map_err(de::Error::custom)
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use std::str::FromStr;
309
310 use crate::core::{Region, WhatAmI};
311
312 #[test]
313 fn test_region_parsing() {
314 assert!(Region::from_str("north:0:router").is_err());
315 assert!(Region::from_str("south").is_err());
316 assert!(Region::from_str("south:42").is_err());
317 assert!(Region::from_str("south:42:broker").is_err());
318 assert!(Region::from_str("south:0.1:router").is_err());
319 assert!(Region::from_str("south:3:router:???").is_err());
320
321 assert_eq!(Region::from_str("north").unwrap(), Region::North);
322 assert_eq!(Region::from_str("local").unwrap(), Region::Local);
323 assert_eq!(
324 Region::from_str("south:2000:peer").unwrap(),
325 Region::South {
326 id: 2000,
327 mode: WhatAmI::Peer
328 }
329 );
330 }
331
332 #[test]
333 fn test_region_formatting() {
334 assert_eq!(&format!("{}", Region::North), "north");
335 assert_eq!(&format!("{}", Region::Local), "local");
336 assert_eq!(
337 &format!(
338 "{}",
339 Region::South {
340 id: 1,
341 mode: WhatAmI::Client
342 }
343 ),
344 "south:1:client"
345 );
346 assert_eq!(
347 &format!(
348 "{}",
349 Region::South {
350 id: 2,
351 mode: WhatAmI::Peer
352 }
353 ),
354 "south:2:peer"
355 );
356 assert_eq!(
357 &format!(
358 "{}",
359 Region::South {
360 id: 3,
361 mode: WhatAmI::Router
362 }
363 ),
364 "south:3:router"
365 );
366 }
367}