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