1use std::borrow::Cow;
2use std::fmt;
3
4use derive_more::{AsRef, Into};
5
6use crate::errors::ErnError;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[derive(AsRef, Into, Eq, Debug, PartialEq, Clone, Hash, PartialOrd)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub struct Part(pub(crate) String);
14
15impl Part {
16 pub fn as_str(&self) -> &str {
17 &self.0
18 }
19
20 pub fn into_owned(self) -> Part {
21 Part(self.0.to_string())
22 }
23
24 pub fn new(value: impl Into<String>) -> Result<Part, ErnError> {
42 let value = value.into();
43
44 if value.contains(':') || value.contains('/') {
46 return Err(ErnError::InvalidPartFormat);
47 }
48
49 if value.is_empty() {
51 return Err(ErnError::ParseFailure(
52 "Part",
53 "cannot be empty".to_string(),
54 ));
55 }
56
57 if value.len() > 63 {
59 return Err(ErnError::ParseFailure(
60 "Part",
61 format!(
62 "length exceeds maximum of 63 characters (got {})",
63 value.len()
64 ),
65 ));
66 }
67
68 let valid_chars = value
70 .chars()
71 .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.');
72
73 if !valid_chars {
74 return Err(ErnError::ParseFailure(
75 "Part",
76 "can only contain alphanumeric characters, hyphens, underscores, and dots"
77 .to_string(),
78 ));
79 }
80
81 Ok(Part(value))
82 }
83}
84
85impl fmt::Display for Part {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "{}", self.0)
88 }
89}
90
91impl std::str::FromStr for Part {
92 type Err = ErnError;
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 Part::new(Cow::Owned(s.to_owned()))
95 }
96}
97
98#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_part_creation() -> anyhow::Result<()> {
110 let part = Part::new("segment")?;
111 assert_eq!(part.as_str(), "segment");
112 Ok(())
113 }
114
115 #[test]
116 fn test_part_display() -> anyhow::Result<()> {
117 let part = Part::new("example")?;
118 assert_eq!(format!("{}", part), "example");
119 Ok(())
120 }
121
122 #[test]
123 fn test_part_from_str() {
124 let part: Part = "test".parse().unwrap();
125 assert_eq!(part.as_str(), "test");
126 }
127
128 #[test]
129 fn test_part_equality() -> anyhow::Result<()> {
130 let part1 = Part::new("segment1")?;
131 let part2 = Part::new("segment1")?;
132 let part3 = Part::new("segment2")?;
133 assert_eq!(part1, part2);
134 assert_ne!(part1, part3);
135 Ok(())
136 }
137
138 #[test]
139 fn test_part_into_string() -> anyhow::Result<()> {
140 let part = Part::new("segment")?;
141 let string: String = part.into();
142 assert_eq!(string, "segment");
143 Ok(())
144 }
145 #[test]
146 fn test_part_validation_too_long() {
147 let long_part = "a".repeat(64);
148 let result = Part::new(long_part);
149 assert!(result.is_err());
150 match result {
151 Err(ErnError::ParseFailure(component, msg)) => {
152 assert_eq!(component, "Part");
153 assert!(msg.contains("length exceeds maximum"));
154 }
155 _ => panic!("Expected ParseFailure error for too long part"),
156 }
157 }
158
159 #[test]
160 fn test_part_validation_invalid_chars() {
161 let result = Part::new("invalid*part");
162 assert!(result.is_err());
163 match result {
164 Err(ErnError::ParseFailure(component, msg)) => {
165 assert_eq!(component, "Part");
166 assert!(msg.contains("can only contain"));
167 }
168 _ => panic!("Expected ParseFailure error for invalid characters"),
169 }
170 }
171
172 #[test]
173 fn test_part_validation_reserved_chars() {
174 let result1 = Part::new("invalid:part");
175 let result2 = Part::new("invalid/part");
176
177 assert!(result1.is_err());
178 assert!(result2.is_err());
179
180 match result1 {
181 Err(ErnError::InvalidPartFormat) => {}
182 _ => panic!("Expected InvalidPartFormat error for part with ':'"),
183 }
184
185 match result2 {
186 Err(ErnError::InvalidPartFormat) => {}
187 _ => panic!("Expected InvalidPartFormat error for part with '/'"),
188 }
189 }
190
191 #[test]
192 fn test_part_validation_valid_complex() -> anyhow::Result<()> {
193 let result = Part::new("valid-part_123.test")?;
194 assert_eq!(result.as_str(), "valid-part_123.test");
195 Ok(())
196 }
197}