1use std::{fmt, str::FromStr};
5
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 constants::{
10 IOTA_NAMES_MAX_LABEL_LENGTH, IOTA_NAMES_MAX_NAME_LENGTH, IOTA_NAMES_MIN_LABEL_LENGTH,
11 IOTA_NAMES_SEPARATOR_AT, IOTA_NAMES_SEPARATOR_DOT, IOTA_NAMES_TLN,
12 },
13 error::IotaNamesError,
14};
15
16#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq)]
17pub struct Name {
18 labels: Vec<String>,
20}
21
22impl FromStr for Name {
23 type Err = IotaNamesError;
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 if s.len() > IOTA_NAMES_MAX_NAME_LENGTH {
27 return Err(IotaNamesError::NameLengthExceeded(
28 s.len(),
29 IOTA_NAMES_MAX_NAME_LENGTH,
30 ));
31 }
32
33 let formatted_string = convert_from_at_format(s, &IOTA_NAMES_SEPARATOR_DOT)?;
34
35 let labels = formatted_string
36 .split(IOTA_NAMES_SEPARATOR_DOT)
37 .rev()
38 .map(validate_label)
39 .collect::<Result<Vec<_>, Self::Err>>()?;
40
41 if labels.len() < 2 {
43 return Err(IotaNamesError::NotEnoughLabels);
44 }
45
46 if labels[0] != IOTA_NAMES_TLN {
47 return Err(IotaNamesError::InvalidTln(labels[0].to_string()));
48 }
49
50 let labels = labels.into_iter().map(ToOwned::to_owned).collect();
51
52 Ok(Name { labels })
53 }
54}
55
56impl fmt::Display for Name {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 let output = self.format(NameFormat::Dot);
61 f.write_str(&output)?;
62
63 Ok(())
64 }
65}
66
67impl Name {
68 pub fn parent(&self) -> Option<Self> {
85 if self.is_subname() {
86 Some(Self {
87 labels: self
88 .labels
89 .iter()
90 .take(self.num_labels() - 1)
91 .cloned()
92 .collect(),
93 })
94 } else {
95 None
96 }
97 }
98
99 pub fn is_sln(&self) -> bool {
101 self.num_labels() == 2
102 }
103
104 pub fn is_subname(&self) -> bool {
106 self.num_labels() >= 3
107 }
108
109 pub fn num_labels(&self) -> usize {
117 self.labels.len()
118 }
119
120 pub fn label(&self, index: usize) -> Option<&String> {
122 self.labels.get(index)
123 }
124
125 pub fn labels(&self) -> &[String] {
128 &self.labels
129 }
130
131 pub fn format(&self, format: NameFormat) -> String {
134 let mut labels = self.labels.clone();
135 let sep = &IOTA_NAMES_SEPARATOR_DOT.to_string();
136 labels.reverse();
137
138 if format == NameFormat::Dot {
139 labels.join(sep)
141 } else {
142 let _tln = labels.pop();
145 let sln = labels.pop().unwrap();
146
147 format!("{}{IOTA_NAMES_SEPARATOR_AT}{sln}", labels.join(sep))
150 }
151 }
152}
153
154#[derive(Clone, Eq, PartialEq, Debug)]
157pub enum NameFormat {
158 At,
159 Dot,
160}
161
162fn convert_from_at_format(s: &str, separator: &char) -> Result<String, IotaNamesError> {
166 let mut splits = s.split(IOTA_NAMES_SEPARATOR_AT);
167
168 let Some(before) = splits.next() else {
169 return Err(IotaNamesError::InvalidSeparator);
170 };
171
172 let Some(after) = splits.next() else {
173 return Ok(before.to_string());
174 };
175
176 if splits.next().is_some() || after.contains(*separator) || after.is_empty() {
177 return Err(IotaNamesError::InvalidSeparator);
178 }
179
180 let mut parts = vec![];
181
182 if !before.is_empty() {
183 parts.push(before);
184 }
185
186 parts.push(after);
187 parts.push(IOTA_NAMES_TLN);
188
189 Ok(parts.join(&separator.to_string()))
190}
191
192pub fn validate_label(label: &str) -> Result<&str, IotaNamesError> {
198 let bytes = label.as_bytes();
199 let len = bytes.len();
200
201 if !(IOTA_NAMES_MIN_LABEL_LENGTH..=IOTA_NAMES_MAX_LABEL_LENGTH).contains(&len) {
202 return Err(IotaNamesError::InvalidLabelLength(
203 len,
204 IOTA_NAMES_MIN_LABEL_LENGTH,
205 IOTA_NAMES_MAX_LABEL_LENGTH,
206 ));
207 }
208
209 for (i, character) in bytes.iter().enumerate() {
210 match character {
211 b'a'..=b'z' | b'0'..=b'9' => continue,
212 b'-' => {
213 if i == 0 || i == len - 1 {
214 return Err(IotaNamesError::HyphensAsFirstOrLastLabelChar);
215 }
216 }
217 _ => return Err(IotaNamesError::InvalidLabelChar((*character) as char, i)),
218 };
219 }
220
221 Ok(label)
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn parent_extraction() {
230 let name = Name::from_str("leaf.node.test.iota")
231 .unwrap()
232 .parent()
233 .unwrap();
234
235 assert_eq!(name.to_string(), "node.test.iota");
236
237 let name = name.parent().unwrap();
238
239 assert_eq!(name.to_string(), "test.iota");
240
241 assert!(name.parent().is_none());
242 }
243
244 #[test]
245 fn name_service_outputs() {
246 assert_eq!("@test".parse::<Name>().unwrap().to_string(), "test.iota");
247 assert_eq!(
248 "test.iota".parse::<Name>().unwrap().to_string(),
249 "test.iota"
250 );
251 assert_eq!(
252 "test@sln".parse::<Name>().unwrap().to_string(),
253 "test.sln.iota"
254 );
255 assert_eq!(
256 "test.test@example".parse::<Name>().unwrap().to_string(),
257 "test.test.example.iota"
258 );
259 assert_eq!(
260 "test.test-with-hyphen@example-hyphen"
261 .parse::<Name>()
262 .unwrap()
263 .to_string(),
264 "test.test-with-hyphen.example-hyphen.iota"
265 );
266 assert_eq!(
267 "iota@iota".parse::<Name>().unwrap().to_string(),
268 "iota.iota.iota"
269 );
270 assert_eq!("@iota".parse::<Name>().unwrap().to_string(), "iota.iota");
271 assert_eq!(
272 "test.test.iota".parse::<Name>().unwrap().to_string(),
273 "test.test.iota"
274 );
275 assert_eq!(
276 "test.test.test.iota".parse::<Name>().unwrap().to_string(),
277 "test.test.test.iota"
278 );
279 assert_eq!(
280 "test.test-with-hyphen.test-with-hyphen.iota"
281 .parse::<Name>()
282 .unwrap()
283 .to_string(),
284 "test.test-with-hyphen.test-with-hyphen.iota"
285 );
286 }
287
288 #[test]
289 fn invalid_inputs() {
290 assert!(".".parse::<Name>().is_err());
291 assert!("@".parse::<Name>().is_err());
292 assert!("@inner.iota".parse::<Name>().is_err());
293 assert!("test@".parse::<Name>().is_err());
294 assert!("iota".parse::<Name>().is_err());
295 assert!("test.test@example.iota".parse::<Name>().is_err());
296 assert!("test@test@example".parse::<Name>().is_err());
297 assert!("test.atoi".parse::<Name>().is_err());
298 assert!("test.test@example-".parse::<Name>().is_err());
299 assert!("test.test@-example".parse::<Name>().is_err());
300 assert!("test.test-@example".parse::<Name>().is_err());
301 assert!("test.-test@example".parse::<Name>().is_err());
302 assert!("test.test-.iota".parse::<Name>().is_err());
303 assert!("test.-test.iota".parse::<Name>().is_err());
304 }
305
306 #[test]
307 fn outputs() {
308 let mut name = "test.iota".parse::<Name>().unwrap();
309 assert!(name.format(NameFormat::Dot) == "test.iota");
310 assert!(name.format(NameFormat::At) == "@test");
311
312 name = "test.test.iota".parse::<Name>().unwrap();
313 assert!(name.format(NameFormat::Dot) == "test.test.iota");
314 assert!(name.format(NameFormat::At) == "test@test");
315
316 name = "test.test.test.iota".parse::<Name>().unwrap();
317 assert!(name.format(NameFormat::Dot) == "test.test.test.iota");
318 assert!(name.format(NameFormat::At) == "test.test@test");
319
320 name = "test.test.test.test.iota".parse::<Name>().unwrap();
321 assert!(name.format(NameFormat::Dot) == "test.test.test.test.iota");
322 assert!(name.format(NameFormat::At) == "test.test.test@test");
323 }
324}