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