Skip to main content

acton_ern/model/
parts.rs

1use std::fmt;
2use std::hash::{Hash, Hasher};
3
4use derive_new::new;
5
6use crate::Part;
7use crate::errors::ErnError;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// The default maximum number of parts in an ERN path.
13///
14/// This bounds incremental construction only. [`Parts::new`] and [`ErnParser`] accept a path
15/// of any length, so raising the bound with [`Parts::add_part_with_limit`] never produces an
16/// ERN that fails to parse back.
17///
18/// Callers that nest deeper than this - a supervision tree, for instance - should use
19/// [`Parts::add_part_with_limit`] or [`Ern::add_part_with_limit`] and choose their own bound.
20///
21/// [`ErnParser`]: crate::ErnParser
22/// [`Ern::add_part_with_limit`]: crate::Ern::add_part_with_limit
23pub const DEFAULT_MAX_PARTS: usize = 10;
24
25/// Represents a collection of parts in the ERN (Entity Resource Name), handling multiple segments.
26#[derive(new, Debug, PartialEq, Clone, Eq, Default, PartialOrd)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub struct Parts(pub(crate) Vec<Part>);
29
30impl Parts {
31    /// Adds a part to the collection, bounded by [`DEFAULT_MAX_PARTS`].
32    ///
33    /// # Arguments
34    ///
35    /// * `part` - The `Part` to be added to the collection.
36    ///
37    /// # Returns
38    ///
39    /// * `Result<Parts, ErnError>` - The updated Parts collection, or an error if the
40    ///   collection already holds [`DEFAULT_MAX_PARTS`] parts.
41    pub fn add_part<T>(self, part: T) -> Result<Self, ErnError>
42    where
43        T: Into<Part>,
44    {
45        self.add_part_with_limit(part, DEFAULT_MAX_PARTS)
46    }
47
48    /// Adds a part to the collection, bounded by a caller-chosen maximum.
49    ///
50    /// Use this when [`DEFAULT_MAX_PARTS`] is the wrong bound for your domain - deep
51    /// supervision hierarchies being the motivating case.
52    ///
53    /// # Arguments
54    ///
55    /// * `part` - The `Part` to be added to the collection.
56    /// * `max_parts` - The maximum number of parts this collection may hold.
57    ///
58    /// # Returns
59    ///
60    /// * `Result<Parts, ErnError>` - The updated Parts collection, or an error if the
61    ///   collection already holds `max_parts` parts.
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// # use acton_ern::prelude::*;
67    /// # fn example() -> Result<(), ErnError> {
68    /// let mut parts = Parts::default();
69    /// for i in 0..32 {
70    ///     parts = parts.add_part_with_limit(Part::new(format!("level{i}"))?, 64)?;
71    /// }
72    /// assert_eq!(parts.len(), 32);
73    /// # Ok(())
74    /// # }
75    /// ```
76    pub fn add_part_with_limit<T>(mut self, part: T, max_parts: usize) -> Result<Self, ErnError>
77    where
78        T: Into<Part>,
79    {
80        // Check if adding another part would exceed the maximum
81        if self.0.len() >= max_parts {
82            return Err(ErnError::ParseFailure(
83                "Parts",
84                format!("cannot exceed maximum of {max_parts} parts"),
85            ));
86        }
87
88        self.0.push(part.into());
89        Ok(self)
90    }
91
92    /// Converts the Parts into an owned version with 'static lifetime
93    pub fn into_owned(self) -> Parts {
94        Parts(self.0.into_iter().collect())
95    }
96
97    /// Returns the number of parts in the collection.
98    pub fn len(&self) -> usize {
99        self.0.len()
100    }
101
102    /// Returns true if the collection is empty.
103    pub fn is_empty(&self) -> bool {
104        self.0.is_empty()
105    }
106}
107
108impl Hash for Parts {
109    fn hash<H: Hasher>(&self, state: &mut H) {
110        self.0.len().hash(state);
111        for part in &self.0 {
112            part.hash(state);
113        }
114    }
115}
116
117impl FromIterator<Part> for Parts {
118    fn from_iter<T: IntoIterator<Item = Part>>(iter: T) -> Self {
119        Parts(iter.into_iter().collect())
120    }
121}
122
123impl fmt::Display for Parts {
124    /// Formats the collection of parts as a string, joining them with '/'.
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(
127            f,
128            "{}",
129            self.0
130                .iter()
131                .map(|p| p.as_str())
132                .collect::<Vec<_>>()
133                .join("/")
134        )
135    }
136}
137
138impl IntoIterator for Parts {
139    type Item = Part;
140    type IntoIter = std::vec::IntoIter<Self::Item>;
141
142    fn into_iter(self) -> Self::IntoIter {
143        self.0.into_iter()
144    }
145}
146
147impl<'a> IntoIterator for &'a Parts {
148    type Item = &'a Part;
149    type IntoIter = std::slice::Iter<'a, Part>;
150
151    fn into_iter(self) -> Self::IntoIter {
152        self.0.iter()
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_parts_creation() -> anyhow::Result<()> {
162        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
163        assert_eq!(parts.to_string(), "segment1/segment2");
164        Ok(())
165    }
166
167    #[test]
168    fn test_parts_add_part() -> anyhow::Result<()> {
169        let mut parts = Parts::new(vec![Part::new("segment1")?]);
170        parts = parts.add_part(Part::new("segment2")?)?;
171        parts = parts.add_part(Part::new("segment3")?)?;
172
173        assert_eq!(parts.to_string(), "segment1/segment2/segment3");
174        Ok(())
175    }
176
177    #[test]
178    fn test_parts_from_iterator() -> anyhow::Result<()> {
179        let parts: Result<Parts, _> = vec!["segment1", "segment2", "segment3"]
180            .into_iter()
181            .map(Part::new)
182            .collect();
183        match parts {
184            Ok(parts) => {
185                assert_eq!(parts.to_string(), "segment1/segment2/segment3");
186                Ok(())
187            }
188            Err(e) => Err(anyhow::anyhow!(e)),
189        }
190    }
191
192    #[test]
193    fn test_parts_into_owned() -> anyhow::Result<()> {
194        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
195        let owned_parts: Parts = parts;
196        assert_eq!(owned_parts.to_string(), "segment1/segment2");
197        Ok(())
198    }
199
200    #[test]
201    fn test_parts_iterator() -> anyhow::Result<()> {
202        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
203        let collected: Vec<_> = parts.into_iter().collect();
204        assert_eq!(collected.len(), 2);
205        assert_eq!(collected[0].as_str(), "segment1");
206        assert_eq!(collected[1].as_str(), "segment2");
207        Ok(())
208    }
209
210    #[test]
211    fn test_parts_ref_iterator() -> anyhow::Result<()> {
212        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
213        let collected: Vec<_> = (&parts).into_iter().map(|p| p.as_str()).collect();
214        assert_eq!(collected, vec!["segment1", "segment2"]);
215        Ok(())
216    }
217
218    #[test]
219    fn test_parts_for_loop() -> anyhow::Result<()> {
220        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
221        let mut collected = Vec::new();
222        for part in parts {
223            collected.push(part.as_str().to_string());
224        }
225        assert_eq!(
226            collected,
227            vec!["segment1".to_string(), "segment2".to_string()]
228        );
229        Ok(())
230    }
231    #[test]
232    fn test_parts_validation_max_parts() -> anyhow::Result<()> {
233        // Create a Parts with 10 parts (maximum allowed)
234        let mut parts = Parts::new(vec![]);
235        for i in 0..10 {
236            parts = parts.add_part(Part::new(format!("part{}", i))?)?;
237        }
238
239        // Adding an 11th part should fail
240        let result = parts.add_part(Part::new("one_too_many")?);
241        assert!(result.is_err());
242
243        match result {
244            Err(ErnError::ParseFailure(component, msg)) => {
245                assert_eq!(component, "Parts");
246                assert!(msg.contains("cannot exceed maximum"));
247            }
248            _ => panic!("Expected ParseFailure error for too many parts"),
249        }
250
251        Ok(())
252    }
253}