180 lines
4.5 KiB
Rust
180 lines
4.5 KiB
Rust
use std::str::FromStr;
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
pub struct JID {
|
|
// TODO: validate localpart (length, char]
|
|
pub localpart: Option<String>,
|
|
pub domainpart: String,
|
|
pub resourcepart: Option<String>,
|
|
}
|
|
|
|
pub enum JIDError {
|
|
NoResourcePart,
|
|
ParseError(ParseError),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ParseError {
|
|
Empty,
|
|
Malformed(String),
|
|
}
|
|
|
|
impl From<ParseError> 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<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(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<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()))
|
|
)
|
|
}
|
|
}
|