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}