grafos_mq/
uri.rs

1//! MQ URI type: `fabric-mq://pool/topic[/partition]`.
2
3extern crate alloc;
4use alloc::string::String;
5use core::fmt;
6use core::str::FromStr;
7
8/// A parsed MQ URI of the form `fabric-mq://pool/topic[/partition]`.
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub struct MqUri {
11    /// The pool name.
12    pub pool: String,
13    /// The topic name.
14    pub topic: String,
15    /// Optional partition index.
16    pub partition: Option<u32>,
17}
18
19/// Errors that can occur when parsing an [`MqUri`].
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum MqUriError {
22    /// Missing the `fabric-mq://` scheme prefix.
23    MissingScheme,
24    /// The path portion is empty or missing pool/topic segments.
25    InvalidPath,
26    /// The partition segment is not a valid u32.
27    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    /// Create a new URI without a partition.
89    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    /// Create a new URI targeting a specific partition.
98    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        // Parse back
136        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}