summaryrefslogtreecommitdiffstats
path: root/src/jid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/jid.rs')
-rw-r--r--src/jid.rs181
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()))
+ )
+ }
+}