acton_ern/model/
domain.rs1use std::fmt;
2
3use derive_more::{AsRef, From, Into};
4
5use crate::errors::ErnError;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[derive(AsRef, From, Into, Eq, Debug, PartialEq, Clone, Hash, PartialOrd)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct Domain(pub(crate) String);
13
14impl Domain {
15 pub fn as_str(&self) -> &str {
16 &self.0
17 }
18
19 pub fn into_owned(self) -> Domain {
20 Domain(self.0)
21 }
22 pub fn new(value: impl Into<String>) -> Result<Self, ErnError> {
40 let val = value.into();
41
42 if val.is_empty() {
44 return Err(ErnError::ParseFailure(
45 "Domain",
46 "cannot be empty".to_string(),
47 ));
48 }
49
50 if val.len() > 63 {
52 return Err(ErnError::ParseFailure(
53 "Domain",
54 format!(
55 "length exceeds maximum of 63 characters (got {})",
56 val.len()
57 ),
58 ));
59 }
60
61 let valid_chars = val
63 .chars()
64 .all(|c| c.is_alphanumeric() || c == '-' || c == '.');
65
66 if !valid_chars {
67 return Err(ErnError::ParseFailure(
68 "Domain",
69 "can only contain alphanumeric characters, hyphens, and dots".to_string(),
70 ));
71 }
72
73 if val.starts_with('-') || val.ends_with('-') {
75 return Err(ErnError::ParseFailure(
76 "Domain",
77 "cannot start or end with a hyphen".to_string(),
78 ));
79 }
80
81 Ok(Domain(val))
82 }
83}
84
85impl Default for Domain {
86 fn default() -> Self {
87 Domain("acton".to_string())
88 }
89}
90
91impl fmt::Display for Domain {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(f, "{}", self.0)
94 }
95}
96
97impl std::str::FromStr for Domain {
98 type Err = ErnError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Domain::new(s.to_string())
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_domain_creation() {
111 let domain = Domain::new("test").unwrap();
112 assert_eq!(domain.as_str(), "test");
113 }
114
115 #[test]
116 fn test_domain_default() {
117 let domain = Domain::default();
118 assert_eq!(domain.as_str(), "acton");
119 }
120
121 #[test]
122 fn test_domain_display() {
123 let domain = Domain::new("example").unwrap();
124 assert_eq!(format!("{}", domain), "example");
125 }
126
127 #[test]
128 fn test_domain_from_str() {
129 let domain: Domain = "test".parse().unwrap();
130 assert_eq!(domain.as_str(), "test");
131 }
132
133 #[test]
134 fn test_domain_equality() -> anyhow::Result<()> {
135 let domain1 = Domain::new("test")?;
136 let domain2 = Domain::new("test")?;
137 let domain3 = Domain::new("other")?;
138 assert_eq!(domain1, domain2);
139 assert_ne!(domain1, domain3);
140 Ok(())
141 }
142
143 #[test]
144 fn test_domain_into_string() {
145 let domain = Domain::new("test").unwrap();
146 let string: String = domain.into();
147 assert_eq!(string, "test");
148 }
149
150 #[test]
151 fn test_domain_validation_empty() {
152 let result = Domain::new("");
153 assert!(result.is_err());
154 match result {
155 Err(ErnError::ParseFailure(component, msg)) => {
156 assert_eq!(component, "Domain");
157 assert!(msg.contains("empty"));
158 }
159 _ => panic!("Expected ParseFailure error for empty domain"),
160 }
161 }
162
163 #[test]
164 fn test_domain_validation_too_long() {
165 let long_domain = "a".repeat(64);
166 let result = Domain::new(long_domain);
167 assert!(result.is_err());
168 match result {
169 Err(ErnError::ParseFailure(component, msg)) => {
170 assert_eq!(component, "Domain");
171 assert!(msg.contains("length exceeds maximum"));
172 }
173 _ => panic!("Expected ParseFailure error for too long domain"),
174 }
175 }
176
177 #[test]
178 fn test_domain_validation_invalid_chars() {
179 let result = Domain::new("invalid_domain$");
180 assert!(result.is_err());
181 match result {
182 Err(ErnError::ParseFailure(component, msg)) => {
183 assert_eq!(component, "Domain");
184 assert!(msg.contains("can only contain"));
185 }
186 _ => panic!("Expected ParseFailure error for invalid characters"),
187 }
188 }
189
190 #[test]
191 fn test_domain_validation_hyphen_start_end() {
192 let result1 = Domain::new("-invalid");
193 let result2 = Domain::new("invalid-");
194
195 assert!(result1.is_err());
196 assert!(result2.is_err());
197
198 match result1 {
199 Err(ErnError::ParseFailure(component, msg)) => {
200 assert_eq!(component, "Domain");
201 assert!(msg.contains("cannot start or end with a hyphen"));
202 }
203 _ => panic!("Expected ParseFailure error for domain starting with hyphen"),
204 }
205 }
206
207 #[test]
208 fn test_domain_validation_valid_complex() {
209 let result = Domain::new("valid-domain.name123");
210 assert!(result.is_ok());
211 assert_eq!(result.unwrap().as_str(), "valid-domain.name123");
212 }
213}