relay_core/identity/
user.rs1use serde::{
2 Deserialize, Deserializer, Serialize, Serializer,
3 de::{self, Visitor},
4};
5
6use super::{IdentityError as Err, InboxId, canonical_identity_string, is_valid_identity_string};
7use crate::prelude::Address;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct UserId {
13 inbox: Option<InboxId>,
14 id: String,
15}
16
17impl From<Address> for UserId {
18 fn from(address: Address) -> Self {
19 address.user().clone()
20 }
21}
22
23impl TryFrom<&str> for UserId {
24 type Error = Err;
25
26 fn try_from(value: &str) -> Result<Self, Err> {
27 UserId::parse(value)
28 }
29}
30
31impl TryFrom<String> for UserId {
32 type Error = Err;
33
34 fn try_from(value: String) -> Result<Self, Err> {
35 UserId::parse(value)
36 }
37}
38
39impl UserId {
40 pub fn parse(input: impl AsRef<str>) -> Result<Self, Err> {
53 let input = input.as_ref();
54
55 if let Some((inbox_str, user_str)) = input.split_once('#')
56 && !inbox_str.is_empty()
57 {
58 let inbox = InboxId::parse(inbox_str)?;
59 let id = Self::parse_user_id(user_str)?;
60 Ok(UserId {
61 inbox: Some(inbox),
62 id,
63 })
64 } else {
65 let id = Self::parse_user_id(input.trim_start_matches("#"))?;
66 Ok(UserId { inbox: None, id })
67 }
68 }
69
70 fn parse_user_id(input: &str) -> Result<String, Err> {
71 if input.is_empty() {
72 return Err(Err::InvalidUser);
73 }
74 if !is_valid_identity_string(input) {
75 return Err(Err::InvalidIdentityString);
76 }
77
78 Ok(canonical_identity_string(input))
79 }
80
81 pub fn replace(&mut self, new_value: impl AsRef<str>) -> Result<(), Err> {
83 *self = Self::parse(new_value)?;
84 Ok(())
85 }
86
87 pub fn inbox(&self) -> Option<&InboxId> {
89 self.inbox.as_ref()
90 }
91
92 pub fn canonical(&self) -> String {
95 if let Some(inbox) = &self.inbox {
96 format!("{}#{}", inbox.canonical(), self.id)
97 } else {
98 format!("#{}", self.id)
99 }
100 }
101}
102
103impl std::fmt::Display for UserId {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(f, "{}", self.canonical())
106 }
107}
108
109impl Serialize for UserId {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: Serializer,
113 {
114 serializer.serialize_str(&self.canonical())
115 }
116}
117
118impl<'de> Deserialize<'de> for UserId {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: Deserializer<'de>,
122 {
123 struct UserIdVisitor;
124
125 impl<'de> Visitor<'de> for UserIdVisitor {
126 type Value = UserId;
127
128 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
129 f.write_str("a canonical Relay Mail address string")
130 }
131
132 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
133 where
134 E: de::Error,
135 {
136 UserId::parse(value)
137 .map_err(|e| de::Error::custom(format!("invalid address `{}`: {}", value, e)))
138 }
139
140 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
141 where
142 E: de::Error,
143 {
144 self.visit_str(&value)
145 }
146 }
147
148 deserializer.deserialize_str(UserIdVisitor)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn parses_and_canonicalizes() {
158 assert_eq!(
159 UserId::parse("Alice.Smith").unwrap().canonical(),
160 "#alice.smith"
161 );
162 }
163
164 #[test]
165 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidUser")]
166 fn rejects_empty() {
167 let _ = UserId::parse("").unwrap();
168 }
169
170 #[test]
171 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
172 fn rejects_invalid_chars() {
173 let _ = UserId::parse("Invalid User!").unwrap();
174 }
175
176 #[test]
177 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
178 fn reject_untrimmed() {
179 let _ = UserId::parse(" trim_me ").unwrap();
180 }
181
182 #[test]
183 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
184 fn rejects_non_ascii() {
185 let _ = UserId::parse("调试输出").unwrap();
186 }
187
188 #[test]
189 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
190 fn rejects_homoglyphs() {
191 let _ = UserId::parse("аlice").unwrap(); }
193}