use serde::de::{Deserializer, Error as DeError, Visitor};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Routing {
None,
Account(String),
Forward(String),
Voicemail(String),
Sip(String),
System(String),
Group(String),
Queue(String),
Ivr(String),
Callback(String),
TimeCondition(String),
Disa(String),
Did(String),
Phone(String),
Unknown { tag: String, value: String },
}
impl Routing {
pub fn tag(&self) -> &str {
match self {
Routing::None => "none",
Routing::Account(_) => "account",
Routing::Forward(_) => "fwd",
Routing::Voicemail(_) => "vm",
Routing::Sip(_) => "sip",
Routing::System(_) => "sys",
Routing::Group(_) => "grp",
Routing::Queue(_) => "queue",
Routing::Ivr(_) => "ivr",
Routing::Callback(_) => "cb",
Routing::TimeCondition(_) => "tc",
Routing::Disa(_) => "disa",
Routing::Did(_) => "did",
Routing::Phone(_) => "phone",
Routing::Unknown { tag, .. } => tag,
}
}
pub fn value(&self) -> &str {
match self {
Routing::None => "",
Routing::Account(v)
| Routing::Forward(v)
| Routing::Voicemail(v)
| Routing::Sip(v)
| Routing::System(v)
| Routing::Group(v)
| Routing::Queue(v)
| Routing::Ivr(v)
| Routing::Callback(v)
| Routing::TimeCondition(v)
| Routing::Disa(v)
| Routing::Did(v)
| Routing::Phone(v) => v,
Routing::Unknown { value, .. } => value,
}
}
}
impl fmt::Display for Routing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.tag(), self.value())
}
}
impl FromStr for Routing {
type Err = RoutingParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (tag, value) = match s.find(':') {
Some(i) => (&s[..i], &s[i + 1..]),
None => return Err(RoutingParseError::MissingColon),
};
Ok(match tag {
"none" => Routing::None,
"account" => Routing::Account(value.into()),
"fwd" => Routing::Forward(value.into()),
"vm" => Routing::Voicemail(value.into()),
"sip" => Routing::Sip(value.into()),
"sys" => Routing::System(value.into()),
"grp" => Routing::Group(value.into()),
"queue" => Routing::Queue(value.into()),
"ivr" => Routing::Ivr(value.into()),
"cb" => Routing::Callback(value.into()),
"tc" => Routing::TimeCondition(value.into()),
"disa" => Routing::Disa(value.into()),
"did" => Routing::Did(value.into()),
"phone" => Routing::Phone(value.into()),
other => Routing::Unknown {
tag: other.to_string(),
value: value.to_string(),
},
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RoutingParseError {
MissingColon,
}
impl fmt::Display for RoutingParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RoutingParseError::MissingColon => {
f.write_str("routing string is missing required `:` separator")
}
}
}
}
impl std::error::Error for RoutingParseError {}
impl Serialize for Routing {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Routing {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct RoutingVisitor;
impl<'de> Visitor<'de> for RoutingVisitor {
type Value = Routing;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a voip.ms routing string of the form `tag:value`")
}
fn visit_str<E>(self, v: &str) -> Result<Routing, E>
where
E: DeError,
{
Routing::from_str(v).map_err(E::custom)
}
fn visit_string<E>(self, v: String) -> Result<Routing, E>
where
E: DeError,
{
Routing::from_str(&v).map_err(E::custom)
}
}
deserializer.deserialize_str(RoutingVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_documented_tags() {
assert_eq!(Routing::from_str("none:").unwrap(), Routing::None);
assert_eq!(
Routing::from_str("account:100001_VoIP").unwrap(),
Routing::Account("100001_VoIP".into()),
);
assert_eq!(
Routing::from_str("fwd:15555").unwrap(),
Routing::Forward("15555".into()),
);
assert_eq!(
Routing::from_str("vm:101").unwrap(),
Routing::Voicemail("101".into()),
);
assert_eq!(
Routing::from_str("cb:2359").unwrap(),
Routing::Callback("2359".into()),
);
}
#[test]
fn preserves_unknown_tags() {
let r = Routing::from_str("future:abc").unwrap();
assert_eq!(
r,
Routing::Unknown {
tag: "future".into(),
value: "abc".into(),
},
);
assert_eq!(r.to_string(), "future:abc");
}
#[test]
fn sip_value_can_contain_colons() {
let r = Routing::from_str("sip:5552223333@sip.voip.ms:5060").unwrap();
assert_eq!(r, Routing::Sip("5552223333@sip.voip.ms:5060".into()));
assert_eq!(r.to_string(), "sip:5552223333@sip.voip.ms:5060");
}
#[test]
fn rejects_missing_colon() {
assert_eq!(
Routing::from_str("nocolon"),
Err(RoutingParseError::MissingColon),
);
}
#[test]
fn round_trips_through_serde() {
let r = Routing::Forward("19998887777".into());
let json = serde_json::to_string(&r).unwrap();
assert_eq!(json, "\"fwd:19998887777\"");
let back: Routing = serde_json::from_str(&json).unwrap();
assert_eq!(back, r);
}
#[test]
fn deserialize_none() {
let r: Routing = serde_json::from_str("\"none:\"").unwrap();
assert_eq!(r, Routing::None);
}
}