acton_ern/
builder.rs

1use std::hash::Hash;
2use std::str::FromStr;
3
4use crate::EntityRoot;
5use crate::errors::ErnError;
6use crate::model::{Account, Category, Domain, Ern, Part, Parts};
7use crate::traits::ErnComponent;
8
9/// A type-safe builder for constructing ERN instances.
10///
11/// `ErnBuilder` uses a state-driven approach to ensure that ERN components are added
12/// in the correct order and with proper validation. The generic `State` parameter
13/// tracks which component should be added next, providing compile-time guarantees
14/// that ERNs are constructed correctly.
15///
16/// # Example
17///
18/// ```
19/// # use acton_ern::prelude::*;
20/// # fn example() -> Result<(), ErnError> {
21/// let ern = ErnBuilder::new()
22///     .with::<Domain>("my-app")?
23///     .with::<Category>("users")?
24///     .with::<Account>("tenant123")?
25///     .with::<EntityRoot>("profile")?
26///     .with::<Part>("settings")?
27///     .build()?;
28/// # Ok(())
29/// # }
30/// ```
31pub struct ErnBuilder<State> {
32    builder: PrivateErnBuilder,
33    _marker: std::marker::PhantomData<State>,
34}
35
36/// Implementation of `ErnBuilder` for the initial state.
37impl ErnBuilder<()> {
38    /// Creates a new ERN builder to start the construction process.
39    ///
40    /// This is always the first step when creating an ERN.
41    ///
42    /// # Example
43    ///
44    /// ```
45    /// # use acton_ern::prelude::*;
46    /// let builder = ErnBuilder::new();
47    /// ```
48    pub fn new() -> ErnBuilder<Domain> {
49        ErnBuilder {
50            builder: PrivateErnBuilder::new(),
51            _marker: std::marker::PhantomData,
52        }
53    }
54}
55
56/// Implementation for the `Part` state, allowing finalization of the ERN.
57impl ErnBuilder<Part> {
58    /// Finalizes the building process and constructs the ERN.
59    ///
60    /// This method is available after at least one `Part` has been added.
61    ///
62    /// # Returns
63    ///
64    /// * `Ok(Ern)` - The fully constructed ERN
65    /// * `Err(ErnError)` - If any validation fails
66    pub fn build(self) -> Result<Ern, ErnError> {
67        self.builder.build()
68    }
69}
70
71/// Implementation for the `Parts` state, allowing finalization of the ERN.
72impl ErnBuilder<Parts> {
73    /// Finalizes the building process and constructs the ERN.
74    ///
75    /// This method is available after multiple `Part`s have been added.
76    ///
77    /// # Returns
78    ///
79    /// * `Ok(Ern)` - The fully constructed ERN
80    /// * `Err(ErnError)` - If any validation fails
81    pub fn build(self) -> Result<Ern, ErnError> {
82        self.builder.build()
83    }
84}
85
86/// Generic implementation for all component states.
87impl<Component: ErnComponent + Hash + Clone + PartialEq + Eq> ErnBuilder<Component> {
88    /// Adds the next component to the ERN, transitioning to the appropriate state.
89    ///
90    /// The type parameter `N` determines which component is being added and ensures
91    /// components are added in the correct order.
92    ///
93    /// # Arguments
94    ///
95    /// * `part` - The string value for this component
96    ///
97    /// # Returns
98    ///
99    /// * `Ok(ErnBuilder<NextState>)` - The builder in its next state
100    /// * `Err(ErnError)` - If the component value is invalid
101    pub fn with<N>(
102        self,
103        part: impl Into<String>,
104    ) -> Result<ErnBuilder<N::NextState>, ErnError>
105    where
106        N: ErnComponent<NextState=Component::NextState> + Hash,
107    {
108        Ok(ErnBuilder {
109            builder: self.builder.add_part(N::prefix(), part.into())?,
110            _marker: std::marker::PhantomData,
111        })
112    }
113}
114
115/// Internal implementation for building ERNs.
116struct PrivateErnBuilder {
117    domain: Option<Domain>,
118    category: Option<Category>,
119    account: Option<Account>,
120    root: Option<EntityRoot>,
121    parts: Parts,
122}
123
124impl PrivateErnBuilder {
125    /// Constructs a new private ERN (Entity Resource Name) builder.
126    fn new() -> Self {
127        Self {
128            domain: None,
129            category: None,
130            account: None,
131            root: None,
132            parts: Parts::new(Vec::new()),
133        }
134    }
135
136    fn add_part(mut self, prefix: &'static str, part: String) -> Result<Self, ErnError> {
137        match prefix {
138            p if p == Domain::prefix() => {
139                self.domain = Some(Domain::new(part)?);
140            }
141            "" => {
142                if self.domain.is_some() && self.category.is_none() {
143                    self.category = Some(Category::new(part)?);
144                } else if self.category.is_some() && self.account.is_none() {
145                    self.account = Some(Account::new(part)?);
146                } else if self.account.is_some() && self.root.is_none() {
147                    self.root = Some(EntityRoot::from_str(part.as_str()).unwrap());
148                } else {
149                    // add the first part
150                    self.parts = self.parts.add_part(Part::new(part)?)?;
151                }
152            }
153            ":" => {
154                self.parts = self.parts.add_part(Part::new(part)?)?;
155            }
156            _ => return Err(ErnError::InvalidPrefix(prefix.to_string())),
157        }
158        Ok(self)
159    }
160
161    /// Finalizes and builds the ERN (Entity Resource Name).
162    fn build(self) -> Result<Ern, ErnError> {
163        let domain = self
164            .domain
165            .ok_or(ErnError::MissingPart("domain".to_string()))?;
166        let category = self
167            .category
168            .ok_or(ErnError::MissingPart("category".to_string()))?;
169        let account = self
170            .account
171            .ok_or(ErnError::MissingPart("account".to_string()))?;
172        let root = self.root.ok_or(ErnError::MissingPart("root".to_string()))?;
173
174        Ok(Ern::new(domain, category, account, root, self.parts))
175    }
176}
177