1use serde::de::{Deserializer, Error as DeError, Visitor};
8use serde::ser::Serializer;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::str::FromStr;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum Routing {
41 None,
43 Account(String),
45 Forward(String),
47 Voicemail(String),
49 Sip(String),
51 System(String),
53 Group(String),
55 Queue(String),
57 Ivr(String),
59 Callback(String),
61 TimeCondition(String),
63 Disa(String),
65 Did(String),
67 Phone(String),
69 Unknown { tag: String, value: String },
72}
73
74impl Routing {
75 pub fn tag(&self) -> &str {
77 match self {
78 Routing::None => "none",
79 Routing::Account(_) => "account",
80 Routing::Forward(_) => "fwd",
81 Routing::Voicemail(_) => "vm",
82 Routing::Sip(_) => "sip",
83 Routing::System(_) => "sys",
84 Routing::Group(_) => "grp",
85 Routing::Queue(_) => "queue",
86 Routing::Ivr(_) => "ivr",
87 Routing::Callback(_) => "cb",
88 Routing::TimeCondition(_) => "tc",
89 Routing::Disa(_) => "disa",
90 Routing::Did(_) => "did",
91 Routing::Phone(_) => "phone",
92 Routing::Unknown { tag, .. } => tag,
93 }
94 }
95
96 pub fn value(&self) -> &str {
98 match self {
99 Routing::None => "",
100 Routing::Account(v)
101 | Routing::Forward(v)
102 | Routing::Voicemail(v)
103 | Routing::Sip(v)
104 | Routing::System(v)
105 | Routing::Group(v)
106 | Routing::Queue(v)
107 | Routing::Ivr(v)
108 | Routing::Callback(v)
109 | Routing::TimeCondition(v)
110 | Routing::Disa(v)
111 | Routing::Did(v)
112 | Routing::Phone(v) => v,
113 Routing::Unknown { value, .. } => value,
114 }
115 }
116}
117
118impl fmt::Display for Routing {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "{}:{}", self.tag(), self.value())
121 }
122}
123
124impl FromStr for Routing {
129 type Err = RoutingParseError;
130
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 let (tag, value) = match s.find(':') {
133 Some(i) => (&s[..i], &s[i + 1..]),
134 None => return Err(RoutingParseError::MissingColon),
135 };
136
137 Ok(match tag {
138 "none" => Routing::None,
139 "account" => Routing::Account(value.into()),
140 "fwd" => Routing::Forward(value.into()),
141 "vm" => Routing::Voicemail(value.into()),
142 "sip" => Routing::Sip(value.into()),
143 "sys" => Routing::System(value.into()),
144 "grp" => Routing::Group(value.into()),
145 "queue" => Routing::Queue(value.into()),
146 "ivr" => Routing::Ivr(value.into()),
147 "cb" => Routing::Callback(value.into()),
148 "tc" => Routing::TimeCondition(value.into()),
149 "disa" => Routing::Disa(value.into()),
150 "did" => Routing::Did(value.into()),
151 "phone" => Routing::Phone(value.into()),
152 other => Routing::Unknown {
153 tag: other.to_string(),
154 value: value.to_string(),
155 },
156 })
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum RoutingParseError {
163 MissingColon,
165}
166
167impl fmt::Display for RoutingParseError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 RoutingParseError::MissingColon => {
171 f.write_str("routing string is missing required `:` separator")
172 }
173 }
174 }
175}
176
177impl std::error::Error for RoutingParseError {}
178
179impl Serialize for Routing {
180 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181 where
182 S: Serializer,
183 {
184 serializer.collect_str(self)
185 }
186}
187
188impl<'de> Deserialize<'de> for Routing {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: Deserializer<'de>,
192 {
193 struct RoutingVisitor;
194
195 impl<'de> Visitor<'de> for RoutingVisitor {
196 type Value = Routing;
197
198 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.write_str("a voip.ms routing string of the form `tag:value`")
200 }
201
202 fn visit_str<E>(self, v: &str) -> Result<Routing, E>
203 where
204 E: DeError,
205 {
206 Routing::from_str(v).map_err(E::custom)
207 }
208
209 fn visit_string<E>(self, v: String) -> Result<Routing, E>
210 where
211 E: DeError,
212 {
213 Routing::from_str(&v).map_err(E::custom)
214 }
215 }
216
217 deserializer.deserialize_str(RoutingVisitor)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn parses_documented_tags() {
227 assert_eq!(Routing::from_str("none:").unwrap(), Routing::None);
228 assert_eq!(
229 Routing::from_str("account:100001_VoIP").unwrap(),
230 Routing::Account("100001_VoIP".into()),
231 );
232 assert_eq!(
233 Routing::from_str("fwd:15555").unwrap(),
234 Routing::Forward("15555".into()),
235 );
236 assert_eq!(
237 Routing::from_str("vm:101").unwrap(),
238 Routing::Voicemail("101".into()),
239 );
240 assert_eq!(
241 Routing::from_str("cb:2359").unwrap(),
242 Routing::Callback("2359".into()),
243 );
244 }
245
246 #[test]
247 fn preserves_unknown_tags() {
248 let r = Routing::from_str("future:abc").unwrap();
249 assert_eq!(
250 r,
251 Routing::Unknown {
252 tag: "future".into(),
253 value: "abc".into(),
254 },
255 );
256 assert_eq!(r.to_string(), "future:abc");
257 }
258
259 #[test]
260 fn sip_value_can_contain_colons() {
261 let r = Routing::from_str("sip:5552223333@sip.voip.ms:5060").unwrap();
263 assert_eq!(r, Routing::Sip("5552223333@sip.voip.ms:5060".into()));
264 assert_eq!(r.to_string(), "sip:5552223333@sip.voip.ms:5060");
265 }
266
267 #[test]
268 fn rejects_missing_colon() {
269 assert_eq!(
270 Routing::from_str("nocolon"),
271 Err(RoutingParseError::MissingColon),
272 );
273 }
274
275 #[test]
276 fn round_trips_through_serde() {
277 let r = Routing::Forward("19998887777".into());
278 let json = serde_json::to_string(&r).unwrap();
279 assert_eq!(json, "\"fwd:19998887777\"");
280 let back: Routing = serde_json::from_str(&json).unwrap();
281 assert_eq!(back, r);
282 }
283
284 #[test]
285 fn deserialize_none() {
286 let r: Routing = serde_json::from_str("\"none:\"").unwrap();
287 assert_eq!(r, Routing::None);
288 }
289}