relaygate_destination/
name.rs1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5use crate::DestinationError;
6
7pub const MAX_LABEL_BYTES: usize = 63;
9pub const MAX_DESTINATION_NAME_BYTES: usize = 253;
11
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17#[serde(try_from = "String", into = "String")]
18pub struct Namespace(Box<str>);
19
20impl Namespace {
21 pub fn new(value: &str) -> Result<Self, DestinationError> {
23 if value.contains('.') {
24 return Err(DestinationError::InvalidNamespace);
25 }
26 validate_label(value)?;
27 Ok(Self(value.into()))
28 }
29
30 #[must_use]
31 pub fn as_str(&self) -> &str {
33 &self.0
34 }
35}
36
37impl FromStr for Namespace {
38 type Err = DestinationError;
39
40 fn from_str(value: &str) -> Result<Self, Self::Err> {
41 Self::new(value)
42 }
43}
44
45impl TryFrom<String> for Namespace {
46 type Error = DestinationError;
47
48 fn try_from(value: String) -> Result<Self, Self::Error> {
49 Self::new(&value)
50 }
51}
52
53impl From<Namespace> for String {
54 fn from(value: Namespace) -> Self {
55 value.0.into_string()
56 }
57}
58
59impl fmt::Display for Namespace {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 formatter.write_str(self.as_str())
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69#[serde(try_from = "String", into = "String")]
70pub struct DestinationName(Box<str>);
71
72impl DestinationName {
73 pub fn new(value: &str) -> Result<Self, DestinationError> {
75 if value.len() > MAX_DESTINATION_NAME_BYTES {
76 return Err(DestinationError::DestinationNameLength);
77 }
78 for label in value.split('.') {
79 validate_label(label)?;
80 }
81 Ok(Self(value.into()))
82 }
83
84 #[must_use]
85 pub fn as_str(&self) -> &str {
87 &self.0
88 }
89
90 #[must_use]
93 pub fn is_descendant_of(&self, prefix: &Self) -> bool {
94 self.as_str()
95 .strip_prefix(prefix.as_str())
96 .is_some_and(|suffix| suffix.starts_with('.'))
97 }
98}
99
100impl FromStr for DestinationName {
101 type Err = DestinationError;
102
103 fn from_str(value: &str) -> Result<Self, Self::Err> {
104 Self::new(value)
105 }
106}
107
108impl TryFrom<String> for DestinationName {
109 type Error = DestinationError;
110
111 fn try_from(value: String) -> Result<Self, Self::Error> {
112 Self::new(&value)
113 }
114}
115
116impl From<DestinationName> for String {
117 fn from(value: DestinationName) -> Self {
118 value.0.into_string()
119 }
120}
121
122impl fmt::Display for DestinationName {
123 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
124 formatter.write_str(self.as_str())
125 }
126}
127
128fn validate_label(value: &str) -> Result<(), DestinationError> {
129 let bytes = value.as_bytes();
130 if bytes.is_empty() || bytes.len() > MAX_LABEL_BYTES {
131 return Err(DestinationError::LabelLength);
132 }
133
134 let is_alphanumeric = |byte: &u8| byte.is_ascii_lowercase() || byte.is_ascii_digit();
135 if !bytes.first().is_some_and(is_alphanumeric) || !bytes.last().is_some_and(is_alphanumeric) {
136 return Err(DestinationError::LabelBoundary);
137 }
138 if !bytes
139 .iter()
140 .all(|byte| is_alphanumeric(byte) || *byte == b'-')
141 {
142 return Err(DestinationError::LabelCharacter);
143 }
144 Ok(())
145}