use std::str::FromStr; #[derive(PartialEq, Debug)] struct JID { // TODO: validate localpart (length, char] localpart: Option, domainpart: String, resourcepart: Option, } #[derive(Debug)] enum JIDParseError { Empty, Malformed, } impl JID { fn new(localpart: Option, domainpart: String, resourcepart: Option) -> Self { Self { localpart, domainpart: domainpart.parse().unwrap(), resourcepart, } } } 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(), self.domainpart, 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())) ) } }