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