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())) ) } }