From 2b2306b57ca00dddee93f9accb5ac63381d117e7 Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Tue, 13 Jun 2023 00:46:59 +0100 Subject: initial commit --- src/jid/mod.rs | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/jid/mod.rs (limited to 'src/jid/mod.rs') 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, + domainpart: Domainpart, + resourcepart: Option, +} + +#[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 { + match s.parse::() { + Ok(ip) => Ok(Domainpart::IPLiteral(ip)), + Err(_) => match s.parse::() { + Ok(ip) => Ok(Domainpart::IPv4Address(ip)), + Err(_) => Ok(Domainpart::IFQDN(s.to_owned())), + }, + } + } +} + +impl TryFrom for Domainpart { + type Error = DomainpartParseError; + + fn try_from(value: String) -> Result { + value.parse() + } +} + +#[derive(Debug)] +enum DomainpartParseError {} + +#[derive(Debug)] +enum JIDParseError { + Empty, + Domainpart(DomainpartParseError), + Malformed, +} + +impl JID { + fn new(localpart: Option, domainpart: String, resourcepart: Option) -> 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 { + 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 for JID { + type Error = JIDParseError; + + fn try_from(value: String) -> Result { + 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::().unwrap(), + JID::new( + Some("cel".into()), + "blos.sm".into(), + Some("greenhouse".into()) + ) + ) + } + + #[test] + fn parse_bare_jid() { + assert_eq!( + "cel@blos.sm".parse::().unwrap(), + JID::new(Some("cel".into()), "blos.sm".into(), None) + ) + } + + #[test] + fn parse_domain_jid() { + assert_eq!( + "component.blos.sm".parse::().unwrap(), + JID::new(None, "component.blos.sm".into(), None) + ) + } + + #[test] + fn parse_full_domain_jid() { + assert_eq!( + "component.blos.sm/bot".parse::().unwrap(), + JID::new(None, "component.blos.sm".into(), Some("bot".into())) + ) + } +} -- cgit