summaryrefslogtreecommitdiffstats
path: root/src/jid
diff options
context:
space:
mode:
Diffstat (limited to 'src/jid')
-rw-r--r--src/jid/mod.rs182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/jid/mod.rs b/src/jid/mod.rs
new file mode 100644
index 0000000..c6c157f
--- /dev/null
+++ b/src/jid/mod.rs
@@ -0,0 +1,182 @@
+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()))
+ )
+ }
+}