summaryrefslogblamecommitdiffstats
path: root/src/jid.rs
blob: 65738dcf9251cadc4a9a85a2dc22b14557cf98c1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                      
 

                     
                                  
                
                                              


                                     

 








                                                                    




                           
                
                     
          



              




                                     





                                                    















                                                      


                      
                          



                                                      
                                        








                                                                        
                                                    














                                                                      
                                                    

                 
                                            




                              
                            





                                                             







                                                           





                                                                        
                            























































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