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