dbc_rs/nodes/
nodes_builder.rs

1#[cfg(feature = "alloc")]
2use crate::compat::ToString;
3#[cfg(any(feature = "alloc", feature = "kernel"))]
4use crate::compat::{Box, String, Vec, str_to_string};
5use crate::{error::Error, error::Result, nodes::Nodes};
6
7/// Builder for creating `Nodes` programmatically.
8///
9/// This builder allows you to construct node lists when building DBC files
10/// programmatically. It validates that node names are unique and within limits.
11///
12/// # Examples
13///
14/// ```rust,no_run
15/// use dbc_rs::NodesBuilder;
16///
17/// let nodes = NodesBuilder::new()
18///     .add_node("ECM")
19///     .add_node("TCM")
20///     .add_node("BCM")
21///     .build()?;
22///
23/// assert_eq!(nodes.len(), 3);
24/// assert!(nodes.contains("ECM"));
25/// # Ok::<(), dbc_rs::Error>(())
26/// ```
27///
28/// # Validation
29///
30/// The builder validates:
31/// - Maximum of 256 nodes (DoS protection)
32/// - No duplicate node names (case-sensitive)
33///
34/// # Feature Requirements
35///
36/// This builder requires the `alloc` feature to be enabled.
37#[derive(Debug, Default)]
38pub struct NodesBuilder {
39    nodes: Vec<String>,
40}
41
42impl NodesBuilder {
43    /// Creates a new `NodesBuilder` with an empty node list.
44    ///
45    /// # Examples
46    ///
47    /// ```rust,no_run
48    /// use dbc_rs::NodesBuilder;
49    ///
50    /// let builder = NodesBuilder::new();
51    /// let nodes = builder.build()?;
52    /// assert!(nodes.is_empty());
53    /// # Ok::<(), dbc_rs::Error>(())
54    /// ```
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Adds a single node to the list.
60    ///
61    /// # Arguments
62    ///
63    /// * `node` - The node name (anything that implements `AsRef<str>`)
64    ///
65    /// # Examples
66    ///
67    /// ```rust,no_run
68    /// use dbc_rs::NodesBuilder;
69    ///
70    /// let nodes = NodesBuilder::new()
71    ///     .add_node("ECM")
72    ///     .add_node("TCM")
73    ///     .build()?;
74    /// assert_eq!(nodes.len(), 2);
75    /// # Ok::<(), dbc_rs::Error>(())
76    /// ```
77    #[must_use]
78    pub fn add_node(mut self, node: impl AsRef<str>) -> Self {
79        self.nodes.push(str_to_string(node));
80        self
81    }
82
83    /// Adds multiple nodes from an iterator.
84    ///
85    /// # Arguments
86    ///
87    /// * `nodes` - An iterator of node names (each item must implement `AsRef<str>`)
88    ///
89    /// # Examples
90    ///
91    /// ```rust,no_run
92    /// use dbc_rs::NodesBuilder;
93    ///
94    /// // From a slice
95    /// let nodes = NodesBuilder::new()
96    ///     .add_nodes(&["ECM", "TCM", "BCM"])
97    ///     .build()?;
98    /// assert_eq!(nodes.len(), 3);
99    ///
100    /// // From a vector
101    /// let node_vec = vec!["Node1", "Node2"];
102    /// let nodes2 = NodesBuilder::new()
103    ///     .add_nodes(node_vec.iter())
104    ///     .build()?;
105    /// assert_eq!(nodes2.len(), 2);
106    /// # Ok::<(), dbc_rs::Error>(())
107    /// ```
108    #[must_use]
109    pub fn add_nodes<I, S>(mut self, nodes: I) -> Self
110    where
111        I: IntoIterator<Item = S>,
112        S: AsRef<str>,
113    {
114        self.nodes.extend(nodes.into_iter().map(str_to_string));
115        self
116    }
117
118    /// Clears all nodes from the builder.
119    ///
120    /// # Examples
121    ///
122    /// ```rust,no_run
123    /// use dbc_rs::NodesBuilder;
124    ///
125    /// let nodes = NodesBuilder::new()
126    ///     .add_node("ECM")
127    ///     .add_node("TCM")
128    ///     .clear()
129    ///     .add_node("BCM")
130    ///     .build()?;
131    /// assert_eq!(nodes.len(), 1);
132    /// assert!(nodes.contains("BCM"));
133    /// assert!(!nodes.contains("ECM"));
134    /// # Ok::<(), dbc_rs::Error>(())
135    /// ```
136    #[must_use]
137    pub fn clear(mut self) -> Self {
138        self.nodes.clear();
139        self
140    }
141
142    fn extract_and_validate_nodes(self) -> Result<Vec<String>> {
143        let node_strs: Vec<String> = {
144            #[cfg(all(feature = "kernel", not(feature = "alloc")))]
145            {
146                self.nodes.into_iter().collect()
147            }
148            #[cfg(all(feature = "alloc", not(feature = "kernel")))]
149            {
150                self.nodes.into_iter().map(|s| s.to_string()).collect()
151            }
152        };
153        let node_refs: Vec<&str> = node_strs.iter().map(|s| s.as_str()).collect();
154        super::Nodes::validate_nodes(&node_refs).map_err(|e| match e {
155            crate::error::ParseError::Version(msg) => {
156                Error::Nodes(crate::error::str_to_error_string(msg))
157            }
158            _ => Error::from(e),
159        })?;
160        Ok(node_strs)
161    }
162
163    /// Validates the current builder state without building.
164    ///
165    /// This is useful for checking if the configuration is valid before building.
166    /// Returns a new builder with validated nodes, or an error if validation fails.
167    ///
168    /// # Returns
169    ///
170    /// Returns `Ok(Self)` if validation succeeds, or `Err(Error::Nodes)` if:
171    /// - More than 256 nodes are specified (exceeds maximum limit)
172    /// - Duplicate node names are found (case-sensitive)
173    ///
174    /// # Examples
175    ///
176    /// ```rust,no_run
177    /// use dbc_rs::NodesBuilder;
178    ///
179    /// // Valid configuration
180    /// let builder = NodesBuilder::new()
181    ///     .add_node("ECM")
182    ///     .add_node("TCM");
183    /// assert!(builder.validate().is_ok());
184    ///
185    /// // Invalid: duplicate nodes
186    /// let builder2 = NodesBuilder::new()
187    ///     .add_node("ECM")
188    ///     .add_node("ECM"); // Duplicate
189    /// assert!(builder2.validate().is_err());
190    /// # Ok::<(), dbc_rs::Error>(())
191    /// ```
192    #[must_use = "validation result should be checked"]
193    pub fn validate(self) -> Result<Self> {
194        let node_strs = self.extract_and_validate_nodes()?;
195        Ok(Self { nodes: node_strs })
196    }
197
198    /// Builds the `Nodes` from the builder configuration.
199    ///
200    /// This validates the nodes and constructs a `Nodes` instance with static lifetime.
201    ///
202    /// # Returns
203    ///
204    /// Returns `Ok(Nodes)` if successful, or `Err(Error::Nodes)` if:
205    /// - More than 256 nodes are specified (exceeds maximum limit)
206    /// - Duplicate node names are found (case-sensitive)
207    ///
208    /// # Examples
209    ///
210    /// ```rust,no_run
211    /// use dbc_rs::NodesBuilder;
212    ///
213    /// // Build with nodes
214    /// let nodes = NodesBuilder::new()
215    ///     .add_node("ECM")
216    ///     .add_node("TCM")
217    ///     .build()?;
218    /// assert_eq!(nodes.len(), 2);
219    ///
220    /// // Build empty
221    /// let empty = NodesBuilder::new().build()?;
222    /// assert!(empty.is_empty());
223    /// # Ok::<(), dbc_rs::Error>(())
224    /// ```
225    ///
226    /// # Errors
227    ///
228    /// ```rust,no_run
229    /// use dbc_rs::NodesBuilder;
230    ///
231    /// // Duplicate nodes
232    /// let result = NodesBuilder::new()
233    ///     .add_node("ECM")
234    ///     .add_node("ECM") // Duplicate
235    ///     .build();
236    /// assert!(result.is_err());
237    ///
238    /// // Too many nodes (limit is 256)
239    /// let mut builder = NodesBuilder::new();
240    /// for i in 0..257 {
241    ///     builder = builder.add_node(format!("Node{i}"));
242    /// }
243    /// assert!(builder.build().is_err());
244    /// ```
245    pub fn build(self) -> Result<Nodes<'static>> {
246        let node_strs = self.extract_and_validate_nodes()?;
247        // Convert owned Strings to static references by leaking Box<str>
248        // This is acceptable for builder pattern where the caller owns the data
249        let mut node_refs: Vec<&'static str> = Vec::new();
250        for s in node_strs {
251            let boxed: Box<str> = s.into_boxed_str();
252            node_refs.push(Box::leak(boxed));
253        }
254        // Validate before construction
255        super::Nodes::validate_nodes(&node_refs).map_err(|e| match e {
256            crate::error::ParseError::Version(msg) => {
257                Error::Nodes(crate::error::str_to_error_string(msg))
258            }
259            _ => Error::from(e),
260        })?;
261        Ok(Nodes::new(&node_refs))
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    #![allow(clippy::float_cmp)]
268    use super::*;
269    use crate::{error::Error, error::lang};
270    #[cfg(any(feature = "alloc", feature = "kernel"))]
271    use alloc::format;
272
273    #[test]
274    fn test_nodes_builder_duplicate() {
275        let result = NodesBuilder::new().add_node("ECM").add_node("TCM").add_node("ECM").build();
276        assert!(result.is_err());
277        match result.unwrap_err() {
278            Error::Nodes(msg) => assert!(msg.contains(lang::NODES_DUPLICATE_NAME)),
279            _ => panic!("Expected Nodes error"),
280        }
281    }
282
283    #[test]
284    fn test_nodes_builder_too_many() {
285        let mut builder = NodesBuilder::new();
286        for i in 0..257 {
287            builder = builder.add_node(format!("Node{i}"));
288        }
289        let result = builder.build();
290        assert!(result.is_err());
291        match result.unwrap_err() {
292            Error::Nodes(msg) => {
293                assert!(msg.contains(lang::NODES_TOO_MANY));
294            }
295            _ => panic!("Expected Nodes error"),
296        }
297    }
298
299    #[test]
300    fn test_nodes_builder_add_nodes() {
301        let nodes = NodesBuilder::new().add_nodes(["ECM", "TCM", "BCM"]).build().unwrap();
302        assert_eq!(nodes.len(), 3);
303        assert!(nodes.contains("ECM"));
304        assert!(nodes.contains("TCM"));
305        assert!(nodes.contains("BCM"));
306    }
307
308    #[test]
309    fn test_nodes_builder_add_nodes_iterator() {
310        let node_vec = ["Node1", "Node2", "Node3"];
311        let nodes = NodesBuilder::new().add_nodes(node_vec.iter()).build().unwrap();
312        assert_eq!(nodes.len(), 3);
313        assert!(nodes.contains("Node1"));
314    }
315
316    #[test]
317    fn test_nodes_builder_clear() {
318        let nodes = NodesBuilder::new()
319            .add_node("ECM")
320            .add_node("TCM")
321            .clear()
322            .add_node("BCM")
323            .build()
324            .unwrap();
325        assert_eq!(nodes.len(), 1);
326        assert!(nodes.contains("BCM"));
327        assert!(!nodes.contains("ECM"));
328        assert!(!nodes.contains("TCM"));
329    }
330
331    #[test]
332    fn test_nodes_builder_validate() {
333        let builder = NodesBuilder::new().add_node("ECM").add_node("TCM");
334        let validated = builder.validate().unwrap();
335        let nodes = validated.build().unwrap();
336        assert_eq!(nodes.len(), 2);
337    }
338
339    #[test]
340    fn test_nodes_builder_validate_duplicate() {
341        let builder = NodesBuilder::new().add_node("ECM").add_node("TCM").add_node("ECM");
342        let result = builder.validate();
343        assert!(result.is_err());
344        match result.unwrap_err() {
345            Error::Nodes(msg) => assert!(msg.contains(lang::NODES_DUPLICATE_NAME)),
346            _ => panic!("Expected Nodes error"),
347        }
348    }
349
350    #[test]
351    fn test_nodes_builder_validate_too_many() {
352        let mut builder = NodesBuilder::new();
353        for i in 0..257 {
354            builder = builder.add_node(format!("Node{i}"));
355        }
356        let result = builder.validate();
357        assert!(result.is_err());
358        match result.unwrap_err() {
359            Error::Nodes(msg) => assert!(msg.contains(lang::NODES_TOO_MANY)),
360            _ => panic!("Expected Nodes error"),
361        }
362    }
363
364    #[test]
365    fn test_nodes_builder_empty() {
366        let nodes = NodesBuilder::new().build().unwrap();
367        assert!(nodes.is_empty());
368        assert_eq!(nodes.len(), 0);
369    }
370}