acton_ern/model/
account.rs

1use std::fmt;
2
3use derive_more::{AsRef, From, Into};
4use crate::errors::ErnError;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// Represents an account identifier in the ERN (Entity Resource Name) system.
10#[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    /// Creates a new Account with validation.
19    ///
20    /// # Arguments
21    ///
22    /// * `value` - The account value to validate and create
23    ///
24    /// # Validation Rules
25    ///
26    /// * Account cannot be empty
27    /// * Account must be between 1 and 63 characters
28    /// * Account can only contain alphanumeric characters, hyphens, and underscores
29    /// * Account cannot start or end with a hyphen or underscore
30    ///
31    /// # Returns
32    ///
33    /// * `Ok(Account)` - If validation passes
34    /// * `Err(ErnError)` - If validation fails
35    pub fn new(value: impl Into<String>) -> Result<Self, ErnError> {
36        let val = value.into();
37        
38        // Check if empty
39        if val.is_empty() {
40            return Err(ErnError::ParseFailure("Account", "cannot be empty".to_string()));
41        }
42        
43        // Check length
44        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        // Check for valid characters
52        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        // Check if starts or ends with hyphen or underscore
64        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//
98// impl From<Account> for String {
99//     fn from(domain: Account) -> Self {
100//         domain.0
101//     }
102// }
103
104#[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}