diff options
Diffstat (limited to 'src/jid/mod.rs')
-rw-r--r-- | src/jid/mod.rs | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/jid/mod.rs b/src/jid/mod.rs new file mode 100644 index 0000000..c6c157f --- /dev/null +++ b/src/jid/mod.rs @@ -0,0 +1,182 @@ +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, +}; + +#[derive(PartialEq, Debug)] +struct JID { + // TODO: validate localpart (length, char] + localpart: Option<String>, + domainpart: Domainpart, + resourcepart: Option<String>, +} + +#[derive(PartialEq, Debug)] +enum Domainpart { + IPLiteral(Ipv6Addr), + IPv4Address(Ipv4Addr), + // TODO: domain name type, not string + IFQDN(String), +} + +impl FromStr for Domainpart { + type Err = DomainpartParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.parse::<Ipv6Addr>() { + Ok(ip) => Ok(Domainpart::IPLiteral(ip)), + Err(_) => match s.parse::<Ipv4Addr>() { + Ok(ip) => Ok(Domainpart::IPv4Address(ip)), + Err(_) => Ok(Domainpart::IFQDN(s.to_owned())), + }, + } + } +} + +impl TryFrom<String> for Domainpart { + type Error = DomainpartParseError; + + fn try_from(value: String) -> Result<Self, Self::Error> { + value.parse() + } +} + +#[derive(Debug)] +enum DomainpartParseError {} + +#[derive(Debug)] +enum JIDParseError { + Empty, + Domainpart(DomainpartParseError), + Malformed, +} + +impl JID { + fn new(localpart: Option<String>, domainpart: String, resourcepart: Option<String>) -> Self { + Self { + localpart, + domainpart: domainpart.parse().unwrap(), + resourcepart, + } + } + + fn validate(&self) -> bool { + todo!() + } +} + +impl FromStr for JID { + type Err = JIDParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let split: Vec<&str> = s.split('@').collect(); + match split.len() { + 0 => Err(JIDParseError::Empty), + 1 => { + let split: Vec<&str> = split[0].split('/').collect(); + match split.len() { + 1 => Ok(JID::new(None, split[0].to_string(), None)), + 2 => Ok(JID::new( + None, + split[0].to_string(), + Some(split[1].to_string()), + )), + _ => Err(JIDParseError::Malformed), + } + } + 2 => { + let split2: Vec<&str> = split[1].split('/').collect(); + match split2.len() { + 1 => Ok(JID::new( + Some(split[0].to_string()), + split2[0].to_string(), + None, + )), + 2 => Ok(JID::new( + Some(split[0].to_string()), + split2[0].to_string(), + Some(split2[1].to_string()), + )), + _ => Err(JIDParseError::Malformed), + } + } + _ => Err(JIDParseError::Malformed), + } + } +} + +impl TryFrom<String> for JID { + type Error = JIDParseError; + + fn try_from(value: String) -> Result<Self, Self::Error> { + value.parse() + } +} + +impl std::fmt::Display for JID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}{}{}", + self.localpart.clone().map(|l| l + "@").unwrap_or_default(), + match &self.domainpart { + Domainpart::IPLiteral(addr) => addr.to_string(), + Domainpart::IPv4Address(addr) => addr.to_string(), + Domainpart::IFQDN(domain) => domain.to_owned(), + }, + self.resourcepart + .clone() + .map(|r| "/".to_owned() + &r) + .unwrap_or_default() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn jid_to_string() { + assert_eq!( + JID::new(Some("cel".into()), "blos.sm".into(), None).to_string(), + "cel@blos.sm".to_owned() + ); + } + + #[test] + fn parse_full_jid() { + assert_eq!( + "cel@blos.sm/greenhouse".parse::<JID>().unwrap(), + JID::new( + Some("cel".into()), + "blos.sm".into(), + Some("greenhouse".into()) + ) + ) + } + + #[test] + fn parse_bare_jid() { + assert_eq!( + "cel@blos.sm".parse::<JID>().unwrap(), + JID::new(Some("cel".into()), "blos.sm".into(), None) + ) + } + + #[test] + fn parse_domain_jid() { + assert_eq!( + "component.blos.sm".parse::<JID>().unwrap(), + JID::new(None, "component.blos.sm".into(), None) + ) + } + + #[test] + fn parse_full_domain_jid() { + assert_eq!( + "component.blos.sm/bot".parse::<JID>().unwrap(), + JID::new(None, "component.blos.sm".into(), Some("bot".into())) + ) + } +} |