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