use std::str::FromStr; #[derive(PartialEq, Debug, Clone)] pub struct JID { // TODO: validate localpart (length, char] pub localpart: Option, pub domainpart: String, pub resourcepart: Option, } pub enum JIDError { NoResourcePart, ParseError(ParseError), } #[derive(Debug)] pub enum ParseError { Empty, Malformed(String), } impl From for peanuts::Error { fn from(e: ParseError) -> Self { match e { ParseError::Empty => peanuts::Error::DeserializeError("".to_string()), ParseError::Malformed(e) => peanuts::Error::DeserializeError(e), } } } impl JID { pub fn new( localpart: Option, domainpart: String, resourcepart: Option, ) -> Self { Self { localpart, domainpart: domainpart.parse().unwrap(), resourcepart, } } pub fn as_bare(&self) -> Self { Self { localpart: self.localpart.clone(), domainpart: self.domainpart.clone(), resourcepart: None, } } pub fn as_full(&self) -> Result<&Self, JIDError> { if let Some(_) = self.resourcepart { Ok(&self) } else { Err(JIDError::NoResourcePart) } } } impl FromStr for JID { type Err = ParseError; fn from_str(s: &str) -> Result { let split: Vec<&str> = s.split('@').collect(); match split.len() { 0 => Err(ParseError::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(ParseError::Malformed(s.to_string())), } } 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(ParseError::Malformed(s.to_string())), } } _ => Err(ParseError::Malformed(s.to_string())), } } } impl TryFrom for JID { type Error = ParseError; fn try_from(value: String) -> Result { value.parse() } } impl TryFrom<&str> for JID { type Error = ParseError; fn try_from(value: &str) -> 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())) ) } }