acton_ern/model/
account.rs1use std::fmt;
2
3use crate::errors::ErnError;
4use derive_more::{AsRef, From, Into};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9#[derive(AsRef, From, Into, Eq, Debug, PartialEq, Clone, Hash, PartialOrd)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct Account(pub(crate) String);
13
14impl Account {
15 pub fn as_str(&self) -> &str {
16 &self.0
17 }
18 pub fn new(value: impl Into<String>) -> Result<Self, ErnError> {
36 let val = value.into();
37
38 if val.is_empty() {
40 return Err(ErnError::ParseFailure(
41 "Account",
42 "cannot be empty".to_string(),
43 ));
44 }
45
46 if val.len() > 63 {
48 return Err(ErnError::ParseFailure(
49 "Account",
50 format!(
51 "length exceeds maximum of 63 characters (got {})",
52 val.len()
53 ),
54 ));
55 }
56
57 let valid_chars = val
59 .chars()
60 .all(|c| c.is_alphanumeric() || c == '-' || c == '_');
61
62 if !valid_chars {
63 return Err(ErnError::ParseFailure(
64 "Account",
65 "can only contain alphanumeric characters, hyphens, and underscores".to_string(),
66 ));
67 }
68
69 if val.starts_with(['-', '_'].as_ref()) || val.ends_with(['-', '_'].as_ref()) {
71 return Err(ErnError::ParseFailure(
72 "Account",
73 "cannot start or end with a hyphen or underscore".to_string(),
74 ));
75 }
76
77 Ok(Account(val))
78 }
79 pub fn into_owned(self) -> Account {
80 Account(self.0.to_string())
81 }
82}
83
84impl Default for Account {
85 fn default() -> Self {
86 Account("component".to_string())
87 }
88}
89
90impl fmt::Display for Account {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.0)
93 }
94}
95
96impl std::str::FromStr for Account {
97 type Err = ErnError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Account::new(s)
101 }
102}
103#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_account_creation() -> anyhow::Result<()> {
116 let account = Account::new("test123")?;
117 assert_eq!(account.as_str(), "test123");
118 Ok(())
119 }
120
121 #[test]
122 fn test_account_default() {
123 let account = Account::default();
124 assert_eq!(account.as_str(), "component");
125 }
126
127 #[test]
128 fn test_account_display() -> anyhow::Result<()> {
129 let account = Account::new("example456")?;
130 assert_eq!(format!("{}", account), "example456");
131 Ok(())
132 }
133
134 #[test]
135 fn test_account_from_str() {
136 let account: Account = "test789".parse().unwrap();
137 assert_eq!(account.as_str(), "test789");
138 }
139
140 #[test]
141 fn test_account_equality() -> anyhow::Result<()> {
142 let account1 = Account::new("test123")?;
143 let account2 = Account::new("test123")?;
144 let account3 = Account::new("other456")?;
145 assert_eq!(account1, account2);
146 assert_ne!(account1, account3);
147 Ok(())
148 }
149
150 #[test]
151 fn test_account_into_string() -> anyhow::Result<()> {
152 let account = Account::new("test123")?;
153 let string: String = account.into();
154 assert_eq!(string, "test123");
155 Ok(())
156 }
157
158 #[test]
159 fn test_account_validation_empty() {
160 let result = Account::new("");
161 assert!(result.is_err());
162 match result {
163 Err(ErnError::ParseFailure(component, msg)) => {
164 assert_eq!(component, "Account");
165 assert!(msg.contains("empty"));
166 }
167 _ => panic!("Expected ParseFailure error for empty account"),
168 }
169 }
170
171 #[test]
172 fn test_account_validation_too_long() {
173 let long_account = "a".repeat(64);
174 let result = Account::new(long_account);
175 assert!(result.is_err());
176 match result {
177 Err(ErnError::ParseFailure(component, msg)) => {
178 assert_eq!(component, "Account");
179 assert!(msg.contains("length exceeds maximum"));
180 }
181 _ => panic!("Expected ParseFailure error for too long account"),
182 }
183 }
184
185 #[test]
186 fn test_account_validation_invalid_chars() {
187 let result = Account::new("invalid.account$");
188 assert!(result.is_err());
189 match result {
190 Err(ErnError::ParseFailure(component, msg)) => {
191 assert_eq!(component, "Account");
192 assert!(msg.contains("can only contain"));
193 }
194 _ => panic!("Expected ParseFailure error for invalid characters"),
195 }
196 }
197
198 #[test]
199 fn test_account_validation_hyphen_underscore_start_end() {
200 let result1 = Account::new("-invalid");
201 let result2 = Account::new("invalid-");
202 let result3 = Account::new("_invalid");
203 let result4 = Account::new("invalid_");
204
205 assert!(result1.is_err());
206 assert!(result2.is_err());
207 assert!(result3.is_err());
208 assert!(result4.is_err());
209
210 match result1 {
211 Err(ErnError::ParseFailure(component, msg)) => {
212 assert_eq!(component, "Account");
213 assert!(msg.contains("cannot start or end with a hyphen or underscore"));
214 }
215 _ => panic!("Expected ParseFailure error for account starting with hyphen"),
216 }
217 }
218
219 #[test]
220 fn test_account_validation_valid_complex() -> anyhow::Result<()> {
221 let result = Account::new("valid-account_123")?;
222 assert_eq!(result.as_str(), "valid-account_123");
223 Ok(())
224 }
225}