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