diff options
Diffstat (limited to 'src/jid.rs')
-rw-r--r-- | src/jid.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/jid.rs b/src/jid.rs new file mode 100644 index 0000000..65738dc --- /dev/null +++ b/src/jid.rs @@ -0,0 +1,181 @@ +use std::str::FromStr; + +use serde::Serialize; + +#[derive(PartialEq, Debug, Clone)] +pub struct JID { + // TODO: validate localpart (length, char] + pub localpart: Option<String>, + pub domainpart: String, + pub resourcepart: Option<String>, +} + +impl Serialize for JID { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +pub enum JIDError { + NoResourcePart, + ParseError(ParseError), +} + +#[derive(Debug)] +pub enum ParseError { + Empty, + Malformed, +} + +impl JID { + pub fn new( + localpart: Option<String>, + domainpart: String, + resourcepart: Option<String>, + ) -> 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<Self, Self::Err> { + 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), + } + } + 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), + } + } + _ => Err(ParseError::Malformed), + } + } +} + +impl TryFrom<String> for JID { + type Error = ParseError; + + fn try_from(value: String) -> Result<Self, Self::Error> { + value.parse() + } +} + +impl TryFrom<&str> for JID { + type Error = ParseError; + + fn try_from(value: &str) -> 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(), + 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::<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())) + ) + } +} |