relay_core/identity/
user.rs1use serde::{Deserialize, Serialize};
2
3use crate::prelude::Address;
4
5use super::{IdentityError as Err, canonical_identity_string, is_valid_identity_string};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct UserId(String);
11
12impl From<Address> for UserId {
13 fn from(address: Address) -> Self {
14 address.user.clone()
15 }
16}
17
18impl TryFrom<&str> for UserId {
19 type Error = Err;
20
21 fn try_from(value: &str) -> Result<Self, Err> {
22 UserId::parse(value)
23 }
24}
25
26impl TryFrom<String> for UserId {
27 type Error = Err;
28
29 fn try_from(value: String) -> Result<Self, Err> {
30 UserId::parse(value)
31 }
32}
33
34impl UserId {
35 pub fn parse(input: impl AsRef<str>) -> Result<Self, Err> {
44 let input = input.as_ref();
45
46 if input.is_empty() {
47 return Err(Err::InvalidUser);
48 }
49 if !is_valid_identity_string(input) {
50 return Err(Err::InvalidIdentityString);
51 }
52
53 let canonical = canonical_identity_string(input);
54
55 Ok(Self(canonical))
56 }
57
58 pub fn replace(&mut self, new_value: impl AsRef<str>) -> Result<(), Err> {
60 *self = Self::parse(new_value)?;
61 Ok(())
62 }
63
64 pub fn canonical(&self) -> &str {
67 &self.0
68 }
69}
70
71impl std::fmt::Display for UserId {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}", self.canonical())
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn parses_and_canonicalizes() {
83 assert_eq!(
84 UserId::parse("Alice.Smith").unwrap().canonical(),
85 "alice.smith"
86 );
87 }
88
89 #[test]
90 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidUser")]
91 fn rejects_empty() {
92 let _ = UserId::parse("").unwrap();
93 }
94
95 #[test]
96 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
97 fn rejects_invalid_chars() {
98 let _ = UserId::parse("Invalid User!").unwrap();
99 }
100
101 #[test]
102 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
103 fn reject_untrimmed() {
104 let _ = UserId::parse(" trim_me ").unwrap();
105 }
106
107 #[test]
108 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
109 fn rejects_non_ascii() {
110 let _ = UserId::parse("调试输出").unwrap();
111 }
112
113 #[test]
114 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidIdentityString")]
115 fn rejects_homoglyphs() {
116 let _ = UserId::parse("аlice").unwrap(); }
118}