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