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