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