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!(f, "{}", self.0.iter().map(|p| p.as_str()).collect::<Vec<_>>().join("/"))
87    }
88}
89
90impl IntoIterator for Parts {
91    type Item = Part;
92    type IntoIter = std::vec::IntoIter<Self::Item>;
93
94    fn into_iter(self) -> Self::IntoIter {
95        self.0.into_iter()
96    }
97}
98
99impl<'a> IntoIterator for &'a Parts {
100    type Item = &'a Part;
101    type IntoIter = std::slice::Iter<'a, Part>;
102
103    fn into_iter(self) -> Self::IntoIter {
104        self.0.iter()
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_parts_creation() -> anyhow::Result<()> {
114        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
115        assert_eq!(parts.to_string(), "segment1/segment2");
116        Ok(())
117    }
118
119    #[test]
120    fn test_parts_add_part() -> anyhow::Result<()> {
121        let mut parts = Parts::new(vec![Part::new("segment1")?]);
122        parts = parts.add_part(Part::new("segment2")?)?;
123        parts = parts.add_part(Part::new("segment3")?)?;
124        
125        assert_eq!(parts.to_string(), "segment1/segment2/segment3");
126        Ok(())
127    }
128
129    #[test]
130    fn test_parts_from_iterator() -> anyhow::Result<()> {
131        let parts: Result<Parts, _> = vec!["segment1", "segment2", "segment3"]
132            .into_iter()
133            .map(Part::new)
134            .collect();
135        match parts {
136            Ok(parts) => {
137                assert_eq!(parts.to_string(), "segment1/segment2/segment3");
138                Ok(())
139            }
140            Err(e) => Err(anyhow::anyhow!(e)),
141        }
142    }
143
144    #[test]
145    fn test_parts_into_owned() -> anyhow::Result<()> {
146        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
147        let owned_parts: Parts = parts;
148        assert_eq!(owned_parts.to_string(), "segment1/segment2");
149        Ok(())
150    }
151
152    #[test]
153    fn test_parts_iterator() -> anyhow::Result<()> {
154        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
155        let collected: Vec<_> = parts.into_iter().collect();
156        assert_eq!(collected.len(), 2);
157        assert_eq!(collected[0].as_str(), "segment1");
158        assert_eq!(collected[1].as_str(), "segment2");
159        Ok(())
160    }
161
162    #[test]
163    fn test_parts_ref_iterator() -> anyhow::Result<()> {
164        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
165        let collected: Vec<_> = (&parts).into_iter().map(|p| p.as_str()).collect();
166        assert_eq!(collected, vec!["segment1", "segment2"]);
167        Ok(())
168    }
169
170    #[test]
171    fn test_parts_for_loop() -> anyhow::Result<()> {
172        let parts = Parts::new(vec![Part::new("segment1")?, Part::new("segment2")?]);
173        let mut collected = Vec::new();
174        for part in parts {
175            collected.push(part.as_str().to_string());
176        }
177        assert_eq!(collected, vec!["segment1".to_string(), "segment2".to_string()]);
178        Ok(())
179    }
180    #[test]
181    fn test_parts_validation_max_parts() -> anyhow::Result<()> {
182        // Create a Parts with 10 parts (maximum allowed)
183        let mut parts = Parts::new(vec![]);
184        for i in 0..10 {
185            parts = parts.add_part(Part::new(format!("part{}", i))?)?;
186        }
187        
188        // Adding an 11th part should fail
189        let result = parts.add_part(Part::new("one_too_many")?);
190        assert!(result.is_err());
191        
192        match result {
193            Err(ErnError::ParseFailure(component, msg)) => {
194                assert_eq!(component, "Parts");
195                assert!(msg.contains("cannot exceed maximum"));
196            }
197            _ => panic!("Expected ParseFailure error for too many parts"),
198        }
199        
200        Ok(())
201    }
202}