summaryrefslogblamecommitdiffstats
path: root/src/jid/mod.rs
blob: c6c157feef98a1c8e169eac93e563ff6b011f385 (plain) (tree)





















































































































































































                                                                                                 
use std::{
    net::{Ipv4Addr, Ipv6Addr},
    str::FromStr,
};

#[derive(PartialEq, Debug)]
struct JID {
    // TODO: validate localpart (length, char]
    localpart: Option<String>,
    domainpart: Domainpart,
    resourcepart: Option<String>,
}

#[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<Self, Self::Err> {
        match s.parse::<Ipv6Addr>() {
            Ok(ip) => Ok(Domainpart::IPLiteral(ip)),
            Err(_) => match s.parse::<Ipv4Addr>() {
                Ok(ip) => Ok(Domainpart::IPv4Address(ip)),
                Err(_) => Ok(Domainpart::IFQDN(s.to_owned())),
            },
        }
    }
}

impl TryFrom<String> for Domainpart {
    type Error = DomainpartParseError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        value.parse()
    }
}

#[derive(Debug)]
enum DomainpartParseError {}

#[derive(Debug)]
enum JIDParseError {
    Empty,
    Domainpart(DomainpartParseError),
    Malformed,
}

impl JID {
    fn new(localpart: Option<String>, domainpart: String, resourcepart: Option<String>) -> 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<Self, Self::Err> {
        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<String> for JID {
    type Error = JIDParseError;

    fn try_from(value: String) -> 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(),
            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::<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()))
        )
    }
}