relay_core/identity/
address.rs1use serde::{Deserialize, Serialize};
2
3use super::{AgentId, IdentityError as Err, InboxId, UserId};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub struct Address {
7 pub inbox: Option<InboxId>,
8 pub user: UserId,
9 pub agent: AgentId,
10}
11
12impl TryFrom<&str> for Address {
13 type Error = Err;
14
15 fn try_from(value: &str) -> Result<Self, Err> {
16 Address::parse(value)
17 }
18}
19
20impl TryFrom<String> for Address {
21 type Error = Err;
22
23 fn try_from(value: String) -> Result<Self, Err> {
24 Address::parse(value)
25 }
26}
27
28impl Address {
29 pub fn new(inbox: Option<InboxId>, user: UserId, agent: AgentId) -> Self {
31 Address { inbox, user, agent }
32 }
33
34 pub fn parse(input: impl AsRef<str>) -> Result<Self, Err> {
48 let input = input.as_ref();
49 if input.trim() != input {
50 return Err(Err::InvalidAddress);
51 }
52
53 let (left, agent_str) = input.rsplit_once('@').ok_or(Err::InvalidAddress)?;
54 let agent = AgentId::parse(agent_str)?;
55
56 let (inbox, user_str) = if let Some((inbox_str, user_str)) = left.rsplit_once('#') {
57 let inbox = if inbox_str.is_empty() {
58 None
59 } else {
60 Some(InboxId::parse(inbox_str)?)
61 };
62 (inbox, user_str)
63 } else {
64 (None, left)
65 };
66 let user = UserId::parse(user_str)?;
67
68 Ok(Address { inbox, user, agent })
69 }
70
71 pub fn canonical(&self) -> String {
74 match &self.inbox {
75 Some(inbox) => format!(
76 "{}#{}@{}",
77 inbox.canonical(),
78 self.user.canonical(),
79 self.agent.canonical()
80 ),
81 None => format!("#{}@{}", self.user.canonical(), self.agent.canonical()),
82 }
83 }
84
85 pub fn inbox(&self) -> Option<&InboxId> {
87 self.inbox.as_ref()
88 }
89
90 pub fn user(&self) -> &UserId {
92 &self.user
93 }
94
95 pub fn agent(&self) -> &AgentId {
97 &self.agent
98 }
99}
100
101impl std::fmt::Display for Address {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 write!(f, "{}", self.canonical())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn parses_and_canonicalizes() {
113 assert_eq!(
114 Address::parse("#Bob@example.org").unwrap().canonical(),
115 "#bob@example.org"
116 );
117 assert_eq!(
118 Address::parse("Bob@example.org").unwrap().canonical(),
119 "#bob@example.org"
120 );
121 assert_eq!(
122 Address::parse("wORk#Bob@example.ORG").unwrap().canonical(),
123 "work#bob@example.org"
124 );
125 }
126
127 #[test]
128 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidAddress")]
129 fn rejects_empty() {
130 let _ = Address::parse("").unwrap();
131 }
132
133 #[test]
134 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidAddress")]
135 fn rejects_invalid_chars() {
136 let _ = Address::parse("Invalid Address!").unwrap();
137 }
138
139 #[test]
140 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidAddress")]
141 fn reject_untrimmed() {
142 let _ = Address::parse(" trim_me ").unwrap();
143 }
144
145 #[test]
146 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidAddress")]
147 fn rejects_non_ascii() {
148 let _ = Address::parse("调试输出").unwrap();
149 }
150
151 #[test]
152 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
153 fn rejects_homoglyphs() {
154 let _ = Address::parse("wоrk#аlice@us.exaмple.org").unwrap(); }
156}