1use std::{fmt, str::FromStr};
5
6use iota_types::base_types::IotaAddress;
7use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 constants::{
12 IOTA_NAMES_MAX_LABEL_LENGTH, IOTA_NAMES_MAX_NAME_LENGTH, IOTA_NAMES_MIN_LABEL_LENGTH,
13 IOTA_NAMES_SEPARATOR_AT, IOTA_NAMES_SEPARATOR_DOT, IOTA_NAMES_TLN,
14 },
15 error::IotaNamesError,
16};
17
18#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq)]
19pub struct Name {
20 labels: Vec<String>,
22}
23
24impl FromStr for Name {
25 type Err = IotaNamesError;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 if s.len() > IOTA_NAMES_MAX_NAME_LENGTH {
29 return Err(IotaNamesError::NameLengthExceeded(
30 s.len(),
31 IOTA_NAMES_MAX_NAME_LENGTH,
32 ));
33 }
34
35 let formatted_string = convert_from_at_format(s, &IOTA_NAMES_SEPARATOR_DOT)?;
36
37 let labels = formatted_string
38 .split(IOTA_NAMES_SEPARATOR_DOT)
39 .rev()
40 .map(validate_label)
41 .collect::<Result<Vec<_>, Self::Err>>()?;
42
43 if labels.len() < 2 {
45 return Err(IotaNamesError::NotEnoughLabels);
46 }
47
48 if labels[0] != IOTA_NAMES_TLN {
49 return Err(IotaNamesError::InvalidTln(labels[0].to_string()));
50 }
51
52 let labels = labels.into_iter().map(ToOwned::to_owned).collect();
53
54 Ok(Name { labels })
55 }
56}
57
58impl fmt::Display for Name {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 let output = self.format(NameFormat::Dot);
63 f.write_str(&output)?;
64
65 Ok(())
66 }
67}
68
69impl Name {
70 pub fn type_(package_address: IotaAddress) -> StructTag {
71 const IOTA_NAMES_NAME_MODULE: &IdentStr = ident_str!("name");
72 const IOTA_NAMES_NAME_STRUCT: &IdentStr = ident_str!("Name");
73
74 StructTag {
75 address: package_address.into(),
76 module: IOTA_NAMES_NAME_MODULE.to_owned(),
77 name: IOTA_NAMES_NAME_STRUCT.to_owned(),
78 type_params: vec![],
79 }
80 }
81
82 pub fn parent(&self) -> Option<Self> {
99 if self.is_subname() {
100 Some(Self {
101 labels: self
102 .labels
103 .iter()
104 .take(self.num_labels() - 1)
105 .cloned()
106 .collect(),
107 })
108 } else {
109 None
110 }
111 }
112
113 pub fn is_sln(&self) -> bool {
115 self.num_labels() == 2
116 }
117
118 pub fn is_subname(&self) -> bool {
120 self.num_labels() >= 3
121 }
122
123 pub fn num_labels(&self) -> usize {
131 self.labels.len()
132 }
133
134 pub fn label(&self, index: usize) -> Option<&String> {
136 self.labels.get(index)
137 }
138
139 pub fn labels(&self) -> &[String] {
142 &self.labels
143 }
144
145 pub fn format(&self, format: NameFormat) -> String {
148 let mut labels = self.labels.clone();
149 let sep = &IOTA_NAMES_SEPARATOR_DOT.to_string();
150 labels.reverse();
151
152 if format == NameFormat::Dot {
153 labels.join(sep)
155 } else {
156 let _tln = labels.pop();
159 let sln = labels.pop().unwrap();
160
161 format!("{}{IOTA_NAMES_SEPARATOR_AT}{sln}", labels.join(sep))
164 }
165 }
166}
167
168#[derive(Clone, Eq, PartialEq, Debug)]
171pub enum NameFormat {
172 At,
173 Dot,
174}
175
176fn convert_from_at_format(s: &str, separator: &char) -> Result<String, IotaNamesError> {
180 let mut splits = s.split(IOTA_NAMES_SEPARATOR_AT);
181
182 let Some(before) = splits.next() else {
183 return Err(IotaNamesError::InvalidSeparator);
184 };
185
186 let Some(after) = splits.next() else {
187 return Ok(before.to_string());
188 };
189
190 if splits.next().is_some() || after.contains(*separator) || after.is_empty() {
191 return Err(IotaNamesError::InvalidSeparator);
192 }
193
194 let mut parts = vec![];
195
196 if !before.is_empty() {
197 parts.push(before);
198 }
199
200 parts.push(after);
201 parts.push(IOTA_NAMES_TLN);
202
203 Ok(parts.join(&separator.to_string()))
204}
205
206pub fn validate_label(label: &str) -> Result<&str, IotaNamesError> {
212 let bytes = label.as_bytes();
213 let len = bytes.len();
214
215 if !(IOTA_NAMES_MIN_LABEL_LENGTH..=IOTA_NAMES_MAX_LABEL_LENGTH).contains(&len) {
216 return Err(IotaNamesError::InvalidLabelLength(
217 len,
218 IOTA_NAMES_MIN_LABEL_LENGTH,
219 IOTA_NAMES_MAX_LABEL_LENGTH,
220 ));
221 }
222
223 for (i, character) in bytes.iter().enumerate() {
224 match character {
225 b'a'..=b'z' | b'0'..=b'9' => continue,
226 b'-' => {
227 if i == 0 || i == len - 1 {
228 return Err(IotaNamesError::HyphensAsFirstOrLastLabelChar);
229 }
230 }
231 _ => return Err(IotaNamesError::InvalidLabelChar((*character) as char, i)),
232 };
233 }
234
235 Ok(label)
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn parent_extraction() {
244 let name = Name::from_str("leaf.node.test.iota")
245 .unwrap()
246 .parent()
247 .unwrap();
248
249 assert_eq!(name.to_string(), "node.test.iota");
250
251 let name = name.parent().unwrap();
252
253 assert_eq!(name.to_string(), "test.iota");
254
255 assert!(name.parent().is_none());
256 }
257
258 #[test]
259 fn name_service_outputs() {
260 assert_eq!("@test".parse::<Name>().unwrap().to_string(), "test.iota");
261 assert_eq!(
262 "test.iota".parse::<Name>().unwrap().to_string(),
263 "test.iota"
264 );
265 assert_eq!(
266 "test@sln".parse::<Name>().unwrap().to_string(),
267 "test.sln.iota"
268 );
269 assert_eq!(
270 "test.test@example".parse::<Name>().unwrap().to_string(),
271 "test.test.example.iota"
272 );
273 assert_eq!(
274 "test.test-with-hyphen@example-hyphen"
275 .parse::<Name>()
276 .unwrap()
277 .to_string(),
278 "test.test-with-hyphen.example-hyphen.iota"
279 );
280 assert_eq!(
281 "iota@iota".parse::<Name>().unwrap().to_string(),
282 "iota.iota.iota"
283 );
284 assert_eq!("@iota".parse::<Name>().unwrap().to_string(), "iota.iota");
285 assert_eq!(
286 "test.test.iota".parse::<Name>().unwrap().to_string(),
287 "test.test.iota"
288 );
289 assert_eq!(
290 "test.test.test.iota".parse::<Name>().unwrap().to_string(),
291 "test.test.test.iota"
292 );
293 assert_eq!(
294 "test.test-with-hyphen.test-with-hyphen.iota"
295 .parse::<Name>()
296 .unwrap()
297 .to_string(),
298 "test.test-with-hyphen.test-with-hyphen.iota"
299 );
300 }
301
302 #[test]
303 fn invalid_inputs() {
304 assert!(".".parse::<Name>().is_err());
305 assert!("@".parse::<Name>().is_err());
306 assert!("@inner.iota".parse::<Name>().is_err());
307 assert!("test@".parse::<Name>().is_err());
308 assert!("iota".parse::<Name>().is_err());
309 assert!("test.test@example.iota".parse::<Name>().is_err());
310 assert!("test@test@example".parse::<Name>().is_err());
311 assert!("test.atoi".parse::<Name>().is_err());
312 assert!("test.test@example-".parse::<Name>().is_err());
313 assert!("test.test@-example".parse::<Name>().is_err());
314 assert!("test.test-@example".parse::<Name>().is_err());
315 assert!("test.-test@example".parse::<Name>().is_err());
316 assert!("test.test-.iota".parse::<Name>().is_err());
317 assert!("test.-test.iota".parse::<Name>().is_err());
318 }
319
320 #[test]
321 fn outputs() {
322 let mut name = "test.iota".parse::<Name>().unwrap();
323 assert!(name.format(NameFormat::Dot) == "test.iota");
324 assert!(name.format(NameFormat::At) == "@test");
325
326 name = "test.test.iota".parse::<Name>().unwrap();
327 assert!(name.format(NameFormat::Dot) == "test.test.iota");
328 assert!(name.format(NameFormat::At) == "test@test");
329
330 name = "test.test.test.iota".parse::<Name>().unwrap();
331 assert!(name.format(NameFormat::Dot) == "test.test.test.iota");
332 assert!(name.format(NameFormat::At) == "test.test@test");
333
334 name = "test.test.test.test.iota".parse::<Name>().unwrap();
335 assert!(name.format(NameFormat::Dot) == "test.test.test.test.iota");
336 assert!(name.format(NameFormat::At) == "test.test.test@test");
337 }
338}