//! Parse `username@domain` federated addresses. //! //! A bare `username` (no `@`) is treated as local. #![allow(dead_code)] // federation not yet wired up /// A parsed federated address. #[derive(Debug, Clone, PartialEq, Eq)] pub struct FederatedAddress { pub username: String, pub domain: Option, } impl FederatedAddress { /// Parse a `user@domain` string. Bare `user` → domain is `None`. pub fn parse(input: &str) -> Self { // Split on the *last* '@' so usernames can contain '@' in theory. match input.rsplit_once('@') { Some((user, domain)) if !domain.is_empty() && !user.is_empty() => Self { username: user.to_string(), domain: Some(domain.to_string()), }, _ => Self { username: input.to_string(), domain: None, }, } } /// Returns true if this address refers to a local user (no domain or domain matches local). pub fn is_local(&self, local_domain: &str) -> bool { match &self.domain { None => true, Some(d) => d == local_domain, } } } #[cfg(test)] mod tests { use super::*; #[test] fn bare_username() { let addr = FederatedAddress::parse("alice"); assert_eq!(addr.username, "alice"); assert_eq!(addr.domain, None); assert!(addr.is_local("example.com")); } #[test] fn user_at_domain() { let addr = FederatedAddress::parse("alice@remote.example.com"); assert_eq!(addr.username, "alice"); assert_eq!(addr.domain, Some("remote.example.com".into())); assert!(!addr.is_local("local.example.com")); assert!(addr.is_local("remote.example.com")); } #[test] fn trailing_at_is_bare() { let addr = FederatedAddress::parse("alice@"); assert_eq!(addr.username, "alice@"); assert_eq!(addr.domain, None); } #[test] fn leading_at_is_bare() { let addr = FederatedAddress::parse("@domain.com"); assert_eq!(addr.username, "@domain.com"); assert_eq!(addr.domain, None); } #[test] fn multiple_at_uses_last() { let addr = FederatedAddress::parse("user@org@domain.com"); assert_eq!(addr.username, "user@org"); assert_eq!(addr.domain, Some("domain.com".into())); } }