identity_iota_core/network/
network_name.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use core::convert::TryFrom;
use core::fmt::Display;
use core::fmt::Formatter;
use core::ops::Deref;
use std::fmt::Debug;
use std::str::FromStr;

use serde::Deserialize;
use serde::Serialize;

use crate::error::Error;
use crate::error::Result;

/// Network name compliant with the [`crate::IotaDID`] method specification.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[repr(transparent)]
pub struct NetworkName(String);

impl NetworkName {
  /// The maximum length of a network name.
  pub const MAX_LENGTH: usize = 8;

  /// Validates whether a string is a spec-compliant IOTA DID [`NetworkName`].
  pub fn validate_network_name(name: &str) -> Result<()> {
    Some(())
      .filter(|_| {
        !name.is_empty()
          && (name.len() <= Self::MAX_LENGTH)
          && name.chars().all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit())
      })
      .ok_or_else(|| Error::InvalidNetworkName(name.to_owned()))
  }
}

impl AsRef<str> for NetworkName {
  fn as_ref(&self) -> &str {
    self.0.as_ref()
  }
}

impl Deref for NetworkName {
  type Target = str;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl TryFrom<String> for NetworkName {
  type Error = Error;
  fn try_from(value: String) -> Result<Self> {
    Self::validate_network_name(&value)?;
    Ok(Self(value))
  }
}

impl<'a> TryFrom<&'a str> for NetworkName {
  type Error = Error;
  fn try_from(value: &'a str) -> Result<Self> {
    value.to_string().try_into()
  }
}

impl FromStr for NetworkName {
  type Err = Error;
  fn from_str(name: &str) -> Result<Self> {
    Self::validate_network_name(name)?;
    Ok(Self(name.to_string()))
  }
}

impl Debug for NetworkName {
  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
    f.write_str(self.as_ref())
  }
}

impl Display for NetworkName {
  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
    f.write_str(self.as_ref())
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  // Rules are: at least one character, at most eight characters and may only contain digits and/or lowercase ascii
  // characters.
  const VALID_NETWORK_NAMES: &[&str] = &[
    "main", "dev", "smr", "rms", "test", "foo", "foobar", "123456", "0", "foo42", "bar123", "42foo", "1234567",
    "foobar0",
  ];

  const INVALID_NETWORK_NAMES: &[&str] = &["Main", "fOo", "deV", "féta", "", "  ", "foo ", " foo"];

  #[test]
  fn valid_validate_network_name() {
    for name in VALID_NETWORK_NAMES {
      assert!(NetworkName::validate_network_name(name).is_ok());
    }
  }

  #[test]
  fn invalid_validate_network_name() {
    for name in INVALID_NETWORK_NAMES {
      assert!(NetworkName::validate_network_name(name).is_err());
    }
  }
}