1extern crate alloc;
4use alloc::string::String;
5use core::fmt;
6use core::str::FromStr;
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub struct MqUri {
11 pub pool: String,
13 pub topic: String,
15 pub partition: Option<u32>,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum MqUriError {
22 MissingScheme,
24 InvalidPath,
26 InvalidPartition,
28}
29
30impl fmt::Display for MqUriError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 MqUriError::MissingScheme => write!(f, "missing fabric-mq:// scheme"),
34 MqUriError::InvalidPath => write!(f, "invalid path: need pool/topic"),
35 MqUriError::InvalidPartition => write!(f, "invalid partition index"),
36 }
37 }
38}
39
40const SCHEME: &str = "fabric-mq://";
41
42impl FromStr for MqUri {
43 type Err = MqUriError;
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 let rest = s.strip_prefix(SCHEME).ok_or(MqUriError::MissingScheme)?;
47 let segments: alloc::vec::Vec<&str> = rest.split('/').collect();
48 match segments.len() {
49 2 => {
50 if segments[0].is_empty() || segments[1].is_empty() {
51 return Err(MqUriError::InvalidPath);
52 }
53 Ok(MqUri {
54 pool: String::from(segments[0]),
55 topic: String::from(segments[1]),
56 partition: None,
57 })
58 }
59 3 => {
60 if segments[0].is_empty() || segments[1].is_empty() || segments[2].is_empty() {
61 return Err(MqUriError::InvalidPath);
62 }
63 let part = segments[2]
64 .parse::<u32>()
65 .map_err(|_| MqUriError::InvalidPartition)?;
66 Ok(MqUri {
67 pool: String::from(segments[0]),
68 topic: String::from(segments[1]),
69 partition: Some(part),
70 })
71 }
72 _ => Err(MqUriError::InvalidPath),
73 }
74 }
75}
76
77impl fmt::Display for MqUri {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}{}/{}", SCHEME, self.pool, self.topic)?;
80 if let Some(p) = self.partition {
81 write!(f, "/{}", p)?;
82 }
83 Ok(())
84 }
85}
86
87impl MqUri {
88 pub fn new(pool: &str, topic: &str) -> Self {
90 MqUri {
91 pool: String::from(pool),
92 topic: String::from(topic),
93 partition: None,
94 }
95 }
96
97 pub fn with_partition(pool: &str, topic: &str, partition: u32) -> Self {
99 MqUri {
100 pool: String::from(pool),
101 topic: String::from(topic),
102 partition: Some(partition),
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn parse_without_partition() {
113 let uri: MqUri = "fabric-mq://default/orders".parse().expect("parse");
114 assert_eq!(uri.pool, "default");
115 assert_eq!(uri.topic, "orders");
116 assert_eq!(uri.partition, None);
117 }
118
119 #[test]
120 fn parse_with_partition() {
121 let uri: MqUri = "fabric-mq://prod/events/3".parse().expect("parse");
122 assert_eq!(uri.pool, "prod");
123 assert_eq!(uri.topic, "events");
124 assert_eq!(uri.partition, Some(3));
125 }
126
127 #[test]
128 fn display_roundtrip() {
129 let uri = MqUri::new("pool", "topic");
130 assert_eq!(uri.to_string(), "fabric-mq://pool/topic");
131
132 let uri2 = MqUri::with_partition("pool", "topic", 7);
133 assert_eq!(uri2.to_string(), "fabric-mq://pool/topic/7");
134
135 let parsed: MqUri = uri2.to_string().parse().expect("roundtrip");
137 assert_eq!(parsed, uri2);
138 }
139
140 #[test]
141 fn missing_scheme() {
142 let result = "http://pool/topic".parse::<MqUri>();
143 assert_eq!(result.unwrap_err(), MqUriError::MissingScheme);
144 }
145
146 #[test]
147 fn empty_segments() {
148 assert!("fabric-mq:///topic".parse::<MqUri>().is_err());
149 assert!("fabric-mq://pool/".parse::<MqUri>().is_err());
150 assert!("fabric-mq://pool".parse::<MqUri>().is_err());
151 }
152
153 #[test]
154 fn invalid_partition() {
155 let result = "fabric-mq://pool/topic/abc".parse::<MqUri>();
156 assert_eq!(result.unwrap_err(), MqUriError::InvalidPartition);
157 }
158}