freeswitch_types/variables/
sip_geolocation.rs1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum SipGeolocationRef {
9 Cid(String),
11 Url(String),
13}
14
15impl fmt::Display for SipGeolocationRef {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match self {
18 Self::Cid(id) => write!(f, "<cid:{id}>"),
19 Self::Url(url) => write!(f, "<{url}>"),
20 }
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct SipGeolocation(Vec<SipGeolocationRef>);
40
41impl SipGeolocation {
42 pub fn parse(raw: &str) -> Self {
44 let refs = raw
45 .split(',')
46 .filter_map(|entry| {
47 let entry = entry.trim();
48 let inner = entry
49 .strip_prefix('<')?
50 .strip_suffix('>')?;
51 if inner.is_empty() {
52 return None;
53 }
54 if let Some(id) = inner.strip_prefix("cid:") {
55 Some(SipGeolocationRef::Cid(id.to_string()))
56 } else {
57 Some(SipGeolocationRef::Url(inner.to_string()))
58 }
59 })
60 .collect();
61 Self(refs)
62 }
63
64 pub fn refs(&self) -> &[SipGeolocationRef] {
66 &self.0
67 }
68
69 pub fn len(&self) -> usize {
71 self.0
72 .len()
73 }
74
75 pub fn is_empty(&self) -> bool {
77 self.0
78 .is_empty()
79 }
80
81 pub fn cid(&self) -> Option<&str> {
83 self.0
84 .iter()
85 .find_map(|r| match r {
86 SipGeolocationRef::Cid(id) => Some(id.as_str()),
87 _ => None,
88 })
89 }
90
91 pub fn url(&self) -> Option<&str> {
93 self.0
94 .iter()
95 .find_map(|r| match r {
96 SipGeolocationRef::Url(url) => Some(url.as_str()),
97 _ => None,
98 })
99 }
100
101 pub fn cids(&self) -> impl Iterator<Item = &str> {
103 self.0
104 .iter()
105 .filter_map(|r| match r {
106 SipGeolocationRef::Cid(id) => Some(id.as_str()),
107 _ => None,
108 })
109 }
110
111 pub fn urls(&self) -> impl Iterator<Item = &str> {
113 self.0
114 .iter()
115 .filter_map(|r| match r {
116 SipGeolocationRef::Url(url) => Some(url.as_str()),
117 _ => None,
118 })
119 }
120}
121
122impl fmt::Display for SipGeolocation {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 for (i, r) in self
125 .0
126 .iter()
127 .enumerate()
128 {
129 if i > 0 {
130 f.write_str(", ")?;
131 }
132 write!(f, "{r}")?;
133 }
134 Ok(())
135 }
136}
137
138impl<'a> IntoIterator for &'a SipGeolocation {
139 type Item = &'a SipGeolocationRef;
140 type IntoIter = std::slice::Iter<'a, SipGeolocationRef>;
141
142 fn into_iter(self) -> Self::IntoIter {
143 self.0
144 .iter()
145 }
146}
147
148impl IntoIterator for SipGeolocation {
149 type Item = SipGeolocationRef;
150 type IntoIter = std::vec::IntoIter<SipGeolocationRef>;
151
152 fn into_iter(self) -> Self::IntoIter {
153 self.0
154 .into_iter()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn parse_cid_and_url() {
164 let raw = "<cid:32863354-18b4-4069-bd00-7bced5fc6c9b>, <https://lis.example.com/api/v1/held/test>";
165 let geo = SipGeolocation::parse(raw);
166 assert_eq!(geo.len(), 2);
167 assert_eq!(geo.cid(), Some("32863354-18b4-4069-bd00-7bced5fc6c9b"));
168 assert!(geo
169 .url()
170 .unwrap()
171 .contains("lis.example.com"));
172 }
173
174 #[test]
175 fn single_cid() {
176 let geo = SipGeolocation::parse("<cid:abc-123>");
177 assert_eq!(geo.len(), 1);
178 assert_eq!(geo.cid(), Some("abc-123"));
179 assert!(geo
180 .url()
181 .is_none());
182 }
183
184 #[test]
185 fn single_url() {
186 let geo = SipGeolocation::parse("<https://lis.example.com/location>");
187 assert_eq!(geo.len(), 1);
188 assert!(geo
189 .cid()
190 .is_none());
191 assert_eq!(geo.url(), Some("https://lis.example.com/location"));
192 }
193
194 #[test]
195 fn empty_input() {
196 let geo = SipGeolocation::parse("");
197 assert!(geo.is_empty());
198 }
199
200 #[test]
201 fn empty_brackets_skipped() {
202 let geo = SipGeolocation::parse("<>, <cid:test>");
203 assert_eq!(geo.len(), 1);
204 assert_eq!(geo.cid(), Some("test"));
205 }
206
207 #[test]
208 fn display_roundtrip() {
209 let raw = "<cid:abc-123>, <https://lis.example.com/test>";
210 let geo = SipGeolocation::parse(raw);
211 assert_eq!(geo.to_string(), raw);
212 }
213
214 #[test]
215 fn multiple_cids() {
216 let raw = "<cid:first>, <cid:second>, <https://example.com/loc>";
217 let geo = SipGeolocation::parse(raw);
218 let cids: Vec<_> = geo
219 .cids()
220 .collect();
221 assert_eq!(cids, vec!["first", "second"]);
222 let urls: Vec<_> = geo
223 .urls()
224 .collect();
225 assert_eq!(urls, vec!["https://example.com/loc"]);
226 }
227}