Skip to main content

acton_ern/
parser.rs

1use std::str::FromStr;
2
3use crate::EntityRoot;
4use crate::errors::ErnError;
5use crate::model::{Account, Category, Domain, Ern, Part, Parts};
6
7/// A parser for converting ERN strings into structured `Ern` objects.
8///
9/// The `ErnParser` takes an ERN string in the format `ern:domain:category:account:root/part1/part2/...`
10/// and parses it into its constituent components, performing validation on each part.
11pub struct ErnParser {
12    /// The ERN string to be parsed.
13    ern: String,
14}
15
16impl ErnParser {
17    /// Creates a new `ErnParser` for the given ERN string.
18    ///
19    /// # Arguments
20    ///
21    /// * `ern` - The ERN string to parse, in the format `ern:domain:category:account:root/part1/part2/...`
22    ///
23    /// # Example
24    ///
25    /// ```
26    /// # use acton_ern::prelude::*;
27    /// let parser = ErnParser::new("ern:my-app:users:tenant123:profile/settings".to_string());
28    /// ```
29    pub fn new(ern: String) -> Self {
30        Self { ern }
31    }
32
33    /// Parses the ERN string into a structured `Ern` object.
34    ///
35    /// This method validates the ERN format, extracts each component, and ensures
36    /// all parts meet the validation requirements.
37    ///
38    /// # Returns
39    ///
40    /// * `Ok(Ern)` - A structured Ern object containing all the parsed components
41    /// * `Err(ErnError)` - If the ERN string is invalid or any component fails validation
42    ///
43    /// # Example
44    ///
45    /// ```
46    /// # use acton_ern::prelude::*;
47    /// # fn example() -> Result<(), ErnError> {
48    /// let parser = ErnParser::new("ern:my-app:users:tenant123:profile/settings".to_string());
49    /// let ern = parser.parse()?;
50    ///
51    /// assert_eq!(ern.domain().as_str(), "my-app");
52    /// assert_eq!(ern.category().as_str(), "users");
53    /// assert_eq!(ern.account().as_str(), "tenant123");
54    /// assert_eq!(ern.parts().to_string(), "settings");
55    /// # Ok(())
56    /// # }
57    /// ```
58    pub fn parse(&self) -> Result<Ern, ErnError> {
59        let parts: Vec<String> = self.ern.splitn(5, ':').map(|s| s.to_string()).collect();
60
61        if parts.len() != 5 || parts[0] != "ern" {
62            return Err(ErnError::InvalidFormat);
63        }
64
65        let domain = Domain::from_str(&parts[1])?;
66        let category = Category::from_str(&parts[2])?;
67        let account = Account::from_str(&parts[3])?;
68
69        // Split the root and the path part
70        let root_path: Vec<String> = parts[4].splitn(2, '/').map(|s| s.to_string()).collect();
71        let root_str = root_path[0].clone();
72        let root: EntityRoot = EntityRoot::from_str(root_str.as_str())?;
73
74        // Continue with the path parts
75        let mut ern_parts = Vec::new();
76        if root_path.len() > 1 {
77            let path_parts: Vec<String> = root_path[1].split('/').map(|s| s.to_string()).collect();
78            for part in path_parts.iter() {
79                ern_parts.push(Part::from_str(part)?);
80            }
81        }
82
83        let parts = Parts::new(ern_parts);
84        Ok(Ern::new(domain, category, account, root, parts))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_valid_ern_parsing() {
94        let ern_str = "ern:custom:service:account123:root/resource/subresource".to_string();
95        let parser: ErnParser = ErnParser::new(ern_str);
96        let result = parser.parse();
97
98        assert!(result.is_ok());
99        let ern = result.unwrap();
100        assert_eq!(ern.domain().as_str(), "custom");
101    }
102
103    #[test]
104    fn test_invalid_ern_format() {
105        let ern_str = "invalid:ern:format";
106        let parser: ErnParser = ErnParser::new(ern_str.to_string());
107        let result = parser.parse();
108        assert!(result.is_err());
109        assert_eq!(result.err().unwrap(), ErnError::InvalidFormat);
110        // assert_eq!(result.unwrap_err().to_string(), "Invalid Ern format");
111    }
112
113    #[test]
114    fn test_ern_with_invalid_part() -> anyhow::Result<()> {
115        let ern_str = "ern:domain:category:account:root/invalid:part";
116        let parser: ErnParser = ErnParser::new(ern_str.to_string());
117        let result = parser.parse();
118        assert!(result.is_err());
119        // assert!(result.unwrap_err().to_string().starts_with("Failed to parse Part"));
120        Ok(())
121    }
122
123    #[test]
124    fn test_ern_parsing_with_owned_string() {
125        let ern_str = String::from("ern:custom:service:account123:root/resource");
126        let parser: ErnParser = ErnParser::new(ern_str);
127        let result = parser.parse();
128        assert!(result.is_ok());
129    }
130}