Skip to main content

acton_ern/model/
account.rs

1use std::fmt;
2
3use crate::errors::ErnError;
4use derive_more::{AsRef, From, Into};
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(
41                "Account",
42                "cannot be empty".to_string(),
43            ));
44        }
45
46        // Check length
47        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        // Check for valid characters
58        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        // Check if starts or ends with hyphen or underscore
70        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//
104// impl From<Account> for String {
105//     fn from(domain: Account) -> Self {
106//         domain.0
107//     }
108// }
109
110#[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}