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/// Represents a collection of parts in the ERN (Entity Resource Name), handling multiple segments.
13#[derive(new, Debug, PartialEq, Clone, Eq, Default, PartialOrd)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct Parts(pub(crate) Vec<Part>);
16
17impl Parts {
18    /// Adds a part to the collection.
19    ///
20    /// # Arguments
21    ///
22    /// * `part` - The `Part` to be added to the collection.
23    ///   Adds a part to the collection with validation.
24    ///
25    /// # Arguments
26    ///
27    /// * `part` - The `Part` to be added to the collection.
28    ///
29    /// # Validation Rules
30    ///
31    /// * Maximum of 10 parts allowed in a single Parts collection
32    ///
33    /// # Returns
34    ///
35    /// * `Result<Parts, ErnError>` - The updated Parts collection or an error
36    pub fn add_part<T>(mut self, part: T) -> Result<Self, ErnError>
37    where
38        T: Into<Part>,
39    {
40        // Check if adding another part would exceed the maximum
41        if self.0.len() >= 10 {
42            return Err(ErnError::ParseFailure(
43                "Parts",
44                "cannot exceed maximum of 10 parts".to_string(),
45            ));
46        }
47
48        self.0.push(part.into());
49        Ok(self)
50    }
51
52    /// Converts the Parts into an owned version with 'static lifetime
53    pub fn into_owned(self) -> Parts {
54        Parts(self.0.into_iter().collect())
55    }
56
57    /// Returns the number of parts in the collection.
58    pub fn len(&self) -> usize {
59        self.0.len()
60    }
61
62    /// Returns true if the collection is empty.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66}
67
68impl Hash for Parts {
69    fn hash<H: Hasher>(&self, state: &mut H) {
70        self.0.len().hash(state);
71        for part in &self.0 {
72            part.hash(state);
73        }
74    }
75}
76
77impl FromIterator<Part> for Parts {
78    fn from_iter<T: IntoIterator<Item = Part>>(iter: T) -> Self {
79        Parts(iter.into_iter().collect())
80    }
81}
82
83impl fmt::Display for Parts {
84    /// Formats the collection of parts as a string, joining them with '/'.
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        write!(
87            f,
88            "{}",
89            self.0
90                .iter()
91                .map(|p| p.as_str())
92                .collect::<Vec<_>>()
93                .join("/")
94        )
95    }
96}
97
98impl IntoIterator for Parts {
99    type Item = Part;
100    type IntoIter = std::vec::IntoIter<Self::Item>;
101
102    fn into_iter(self) -> Self::IntoIter {
103        self.0.into_iter()
104    }
105}
106
107impl<'a> IntoIterator for &'a Parts {
108    type Item = &'a Part;
109    type IntoIter = std::slice::Iter<'a, Part>;
110
111    fn into_iter(self) -> Self::IntoIter {
112        self.0.iter()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_parts_creation() -> anyhow::Result<()> {
122        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
123        assert_eq!(parts.to_string(), "segment1/segment2");
124        Ok(())
125    }
126
127    #[test]
128    fn test_parts_add_part() -> anyhow::Result<()> {
129        let mut parts = Parts::new(vec![Part::new("segment1")?]);
130        parts = parts.add_part(Part::new("segment2")?)?;
131        parts = parts.add_part(Part::new("segment3")?)?;
132
133        assert_eq!(parts.to_string(), "segment1/segment2/segment3");
134        Ok(())
135    }
136
137    #[test]
138    fn test_parts_from_iterator() -> anyhow::Result<()> {
139        let parts: Result<Parts, _> = vec!["segment1", "segment2", "segment3"]
140            .into_iter()
141            .map(Part::new)
142            .collect();
143        match parts {
144            Ok(parts) => {
145                assert_eq!(parts.to_string(), "segment1/segment2/segment3");
146                Ok(())
147            }
148            Err(e) => Err(anyhow::anyhow!(e)),
149        }
150    }
151
152    #[test]
153    fn test_parts_into_owned() -> anyhow::Result<()> {
154        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
155        let owned_parts: Parts = parts;
156        assert_eq!(owned_parts.to_string(), "segment1/segment2");
157        Ok(())
158    }
159
160    #[test]
161    fn test_parts_iterator() -> anyhow::Result<()> {
162        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
163        let collected: Vec<_> = parts.into_iter().collect();
164        assert_eq!(collected.len(), 2);
165        assert_eq!(collected[0].as_str(), "segment1");
166        assert_eq!(collected[1].as_str(), "segment2");
167        Ok(())
168    }
169
170    #[test]
171    fn test_parts_ref_iterator() -> anyhow::Result<()> {
172        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
173        let collected: Vec<_> = (&parts).into_iter().map(|p| p.as_str()).collect();
174        assert_eq!(collected, vec!["segment1", "segment2"]);
175        Ok(())
176    }
177
178    #[test]
179    fn test_parts_for_loop() -> anyhow::Result<()> {
180        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
181        let mut collected = Vec::new();
182        for part in parts {
183            collected.push(part.as_str().to_string());
184        }
185        assert_eq!(
186            collected,
187            vec!["segment1".to_string(), "segment2".to_string()]
188        );
189        Ok(())
190    }
191    #[test]
192    fn test_parts_validation_max_parts() -> anyhow::Result<()> {
193        // Create a Parts with 10 parts (maximum allowed)
194        let mut parts = Parts::new(vec![]);
195        for i in 0..10 {
196            parts = parts.add_part(Part::new(format!("part{}", i))?)?;
197        }
198
199        // Adding an 11th part should fail
200        let result = parts.add_part(Part::new("one_too_many")?);
201        assert!(result.is_err());
202
203        match result {
204            Err(ErnError::ParseFailure(component, msg)) => {
205                assert_eq!(component, "Parts");
206                assert!(msg.contains("cannot exceed maximum"));
207            }
208            _ => panic!("Expected ParseFailure error for too many parts"),
209        }
210
211        Ok(())
212    }
213}