dbc_rs/dbc/
dbc_builder.rs

1use crate::{
2    Dbc, Message, Nodes, Version,
3    error::{Error, Result, messages},
4};
5
6/// Builder for constructing `Dbc` instances programmatically.
7///
8/// This builder allows you to create DBC files without parsing from a string.
9/// It requires the `alloc` feature to be enabled.
10///
11/// # Examples
12///
13/// ```rust,no_run
14/// use dbc_rs::{DbcBuilder, NodesBuilder, MessageBuilder, SignalBuilder, VersionBuilder};
15///
16/// let nodes = NodesBuilder::new()
17///     .add_node("ECM")
18///     .add_node("TCM")
19///     .build()?;
20///
21/// let signal = SignalBuilder::new()
22///     .name("RPM")
23///     .start_bit(0)
24///     .length(16)
25///     .build()?;
26///
27/// let message = MessageBuilder::new()
28///     .id(256)
29///     .name("EngineData")
30///     .dlc(8)
31///     .sender("ECM")
32///     .add_signal(signal)
33///     .build()?;
34///
35/// let dbc = DbcBuilder::new()
36///     .version(VersionBuilder::new().version("1.0").build()?)
37///     .nodes(nodes)
38///     .add_message(message)
39///     .build()?;
40/// # Ok::<(), dbc_rs::Error>(())
41/// ```
42#[derive(Debug, Default)]
43pub struct DbcBuilder {
44    version: Option<Version<'static>>,
45    nodes: Option<Nodes<'static>>,
46    messages: Vec<Message<'static>>,
47}
48
49impl DbcBuilder {
50    /// Creates a new `DbcBuilder` with default values.
51    ///
52    /// # Examples
53    ///
54    /// ```rust,no_run
55    /// use dbc_rs::DbcBuilder;
56    ///
57    /// let builder = DbcBuilder::new();
58    /// ```
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Sets the version for the DBC file.
64    ///
65    /// # Examples
66    ///
67    /// ```rust,no_run
68    /// use dbc_rs::{DbcBuilder, VersionBuilder};
69    ///
70    /// let builder = DbcBuilder::new()
71    ///     .version(VersionBuilder::new().version("1.0").build()?);
72    /// # Ok::<(), dbc_rs::Error>(())
73    /// ```
74    #[must_use]
75    pub fn version(mut self, version: Version<'static>) -> Self {
76        self.version = Some(version);
77        self
78    }
79
80    /// Sets the nodes (ECUs) for the DBC file.
81    ///
82    /// # Examples
83    ///
84    /// ```rust,no_run
85    /// use dbc_rs::{DbcBuilder, NodesBuilder};
86    ///
87    /// let builder = DbcBuilder::new()
88    ///     .nodes(NodesBuilder::new().add_node("ECM").build()?);
89    /// # Ok::<(), dbc_rs::Error>(())
90    /// ```
91    #[must_use]
92    pub fn nodes(mut self, nodes: Nodes<'static>) -> Self {
93        self.nodes = Some(nodes);
94        self
95    }
96
97    /// Adds a message to the DBC file.
98    ///
99    /// # Examples
100    ///
101    /// ```rust,no_run
102    /// use dbc_rs::{DbcBuilder, MessageBuilder};
103    ///
104    /// let message = MessageBuilder::new()
105    ///     .id(256)
106    ///     .name("EngineData")
107    ///     .dlc(8)
108    ///     .sender("ECM")
109    ///     .build()?;
110    ///
111    /// let builder = DbcBuilder::new()
112    ///     .add_message(message);
113    /// # Ok::<(), dbc_rs::Error>(())
114    /// ```
115    #[must_use]
116    pub fn add_message(mut self, message: Message<'static>) -> Self {
117        self.messages.push(message);
118        self
119    }
120
121    /// Adds multiple messages to the DBC file.
122    ///
123    /// # Examples
124    ///
125    /// ```rust,no_run
126    /// use dbc_rs::{DbcBuilder, MessageBuilder};
127    ///
128    /// let messages = vec![
129    ///     MessageBuilder::new().id(256).name("Msg1").dlc(8).sender("ECM").build()?,
130    ///     MessageBuilder::new().id(512).name("Msg2").dlc(4).sender("TCM").build()?,
131    /// ];
132    ///
133    /// let builder = DbcBuilder::new()
134    ///     .add_messages(messages);
135    /// # Ok::<(), dbc_rs::Error>(())
136    /// ```
137    #[must_use]
138    pub fn add_messages(mut self, messages: impl IntoIterator<Item = Message<'static>>) -> Self {
139        self.messages.extend(messages);
140        self
141    }
142
143    /// Sets all messages for the DBC file, replacing any existing messages.
144    ///
145    /// # Examples
146    ///
147    /// ```rust,no_run
148    /// use dbc_rs::{DbcBuilder, MessageBuilder};
149    ///
150    /// let messages = vec![
151    ///     MessageBuilder::new().id(256).name("Msg1").dlc(8).sender("ECM").build()?,
152    /// ];
153    ///
154    /// let builder = DbcBuilder::new()
155    ///     .messages(messages);
156    /// # Ok::<(), dbc_rs::Error>(())
157    /// ```
158    #[must_use]
159    pub fn messages(mut self, messages: Vec<Message<'static>>) -> Self {
160        self.messages = messages;
161        self
162    }
163
164    /// Clears all messages from the builder.
165    ///
166    /// # Examples
167    ///
168    /// ```rust,no_run
169    /// use dbc_rs::DbcBuilder;
170    ///
171    /// let builder = DbcBuilder::new()
172    ///     .clear_messages();
173    /// ```
174    #[must_use]
175    pub fn clear_messages(mut self) -> Self {
176        self.messages.clear();
177        self
178    }
179
180    fn extract_fields(self) -> Result<(Version<'static>, Nodes<'static>, Vec<Message<'static>>)> {
181        let version = self
182            .version
183            .ok_or_else(|| Error::Dbc(messages::DBC_VERSION_REQUIRED.to_string()))?;
184        // Allow empty nodes (DBC spec allows empty BU_: line)
185        let nodes = self.nodes.unwrap_or_default();
186        Ok((version, nodes, self.messages))
187    }
188
189    /// Validates the builder without constructing the `Dbc`.
190    ///
191    /// This method performs all validation checks but returns the builder
192    /// instead of constructing the `Dbc`. Useful for checking if the builder
193    /// is valid before calling `build()`.
194    ///
195    /// # Examples
196    ///
197    /// ```rust,no_run
198    /// use dbc_rs::DbcBuilder;
199    ///
200    /// let builder = DbcBuilder::new();
201    /// if builder.validate().is_err() {
202    ///     // Handle validation error
203    /// }
204    /// ```
205    #[must_use = "validation result should be checked"]
206    pub fn validate(self) -> Result<Self> {
207        let (version, nodes, messages) = self.extract_fields()?;
208        // Convert Vec to Option array for validation (all Some)
209        let messages_options: Vec<Option<Message<'static>>> =
210            messages.into_iter().map(Some).collect();
211        let messages_options_slice: &[Option<Message<'static>>] = &messages_options;
212        Dbc::validate(
213            Some(&version),
214            &nodes,
215            messages_options_slice,
216            messages_options_slice.len(),
217        )
218        .map_err(|e| match e {
219            crate::error::ParseError::Version(msg) => Error::Dbc(String::from(msg)),
220            _ => Error::from(e),
221        })?;
222        Ok(Self {
223            version: Some(version),
224            nodes: Some(nodes),
225            messages: messages_options.into_iter().map(|opt| opt.unwrap()).collect(),
226        })
227    }
228
229    /// Builds the `Dbc` from the builder.
230    ///
231    /// This method validates all fields and constructs the `Dbc` instance.
232    /// Returns an error if validation fails.
233    ///
234    /// # Examples
235    ///
236    /// ```rust,no_run
237    /// use dbc_rs::{DbcBuilder, VersionBuilder, NodesBuilder};
238    ///
239    /// let dbc = DbcBuilder::new()
240    ///     .version(VersionBuilder::new().version("1.0").build()?)
241    ///     .nodes(NodesBuilder::new().add_node("ECM").build()?)
242    ///     .build()?;
243    /// # Ok::<(), dbc_rs::Error>(())
244    /// ```
245    pub fn build(self) -> Result<Dbc<'static>> {
246        let (version, nodes, messages) = self.extract_fields()?;
247        // Convert Vec to Option array for validation (all Some)
248        let messages_options: Vec<Option<Message<'static>>> =
249            messages.into_iter().map(Some).collect();
250        let messages_options_slice: &[Option<Message<'static>>] = &messages_options;
251        // Validate before construction
252        Dbc::validate(
253            Some(&version),
254            &nodes,
255            messages_options_slice,
256            messages_options_slice.len(),
257        )
258        .map_err(|e| match e {
259            crate::error::ParseError::Version(msg) => Error::Dbc(String::from(msg)),
260            _ => Error::from(e),
261        })?;
262        // Convert Option array back to Vec for slice creation
263        let messages: Vec<Message<'static>> =
264            messages_options.into_iter().map(|opt| opt.unwrap()).collect();
265        // Convert Vec to slice by leaking the boxed slice to get 'static lifetime
266        let messages_boxed: Box<[Message<'static>]> = messages.into_boxed_slice();
267        let messages_slice: &'static [Message<'static>] = Box::leak(messages_boxed);
268        Ok(Dbc::new(Some(version), nodes, messages_slice))
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    #![allow(clippy::float_cmp)]
275    use super::DbcBuilder;
276    use crate::{ByteOrder, Error, Parser, Version, error::lang, nodes::NodesBuilder};
277    use crate::{MessageBuilder, ReceiversBuilder, SignalBuilder};
278
279    #[test]
280    fn test_dbc_builder_valid() {
281        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
282        let version = Version::parse(&mut parser).unwrap();
283        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
284        let signal = SignalBuilder::new()
285            .name("RPM")
286            .start_bit(0)
287            .length(16)
288            .byte_order(ByteOrder::BigEndian)
289            .unsigned(true)
290            .factor(1.0)
291            .offset(0.0)
292            .min(0.0)
293            .max(100.0)
294            .receivers(ReceiversBuilder::new().none().build().unwrap())
295            .build()
296            .unwrap();
297        let message = MessageBuilder::new()
298            .id(256)
299            .name("EngineData")
300            .dlc(8)
301            .sender("ECM")
302            .add_signal(signal)
303            .build()
304            .unwrap();
305
306        let dbc = DbcBuilder::new()
307            .version(version)
308            .nodes(nodes)
309            .add_message(message)
310            .build()
311            .unwrap();
312
313        assert_eq!(dbc.messages().len(), 1);
314        assert_eq!(dbc.messages().at(0).unwrap().id(), 256);
315    }
316
317    #[test]
318    fn test_dbc_builder_missing_version() {
319        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
320        let signal = SignalBuilder::new()
321            .name("RPM")
322            .start_bit(0)
323            .length(16)
324            .byte_order(ByteOrder::BigEndian)
325            .unsigned(true)
326            .factor(1.0)
327            .offset(0.0)
328            .min(0.0)
329            .max(100.0)
330            .receivers(ReceiversBuilder::new().none().build().unwrap())
331            .build()
332            .unwrap();
333        let message = MessageBuilder::new()
334            .id(256)
335            .name("EngineData")
336            .dlc(8)
337            .sender("ECM")
338            .add_signal(signal)
339            .build()
340            .unwrap();
341
342        let result = DbcBuilder::new().nodes(nodes).add_message(message).build();
343        assert!(result.is_err());
344        match result.unwrap_err() {
345            Error::Dbc(msg) => assert!(msg.contains(lang::DBC_VERSION_REQUIRED)),
346            _ => panic!("Expected Dbc error"),
347        }
348    }
349
350    #[test]
351    fn test_dbc_builder_missing_nodes() {
352        // Empty nodes are now allowed per DBC spec
353        // When nodes are empty, sender validation is skipped
354        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
355        let version = Version::parse(&mut parser).unwrap();
356        let signal = SignalBuilder::new()
357            .name("RPM")
358            .start_bit(0)
359            .length(16)
360            .byte_order(ByteOrder::BigEndian)
361            .unsigned(true)
362            .factor(1.0)
363            .offset(0.0)
364            .min(0.0)
365            .max(100.0)
366            .receivers(ReceiversBuilder::new().none().build().unwrap())
367            .build()
368            .unwrap();
369        let message = MessageBuilder::new()
370            .id(256)
371            .name("EngineData")
372            .dlc(8)
373            .sender("ECM")
374            .add_signal(signal)
375            .build()
376            .unwrap();
377
378        // Building without nodes should succeed (empty nodes allowed)
379        let result = DbcBuilder::new().version(version).add_message(message).build();
380        assert!(result.is_ok());
381        let dbc = result.unwrap();
382        assert!(dbc.nodes().is_empty());
383    }
384
385    #[test]
386    fn test_dbc_builder_add_messages() {
387        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
388        let version = Version::parse(&mut parser).unwrap();
389        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
390        let signal = SignalBuilder::new()
391            .name("RPM")
392            .start_bit(0)
393            .length(16)
394            .byte_order(ByteOrder::BigEndian)
395            .unsigned(true)
396            .factor(1.0)
397            .offset(0.0)
398            .min(0.0)
399            .max(100.0)
400            .receivers(ReceiversBuilder::new().none().build().unwrap())
401            .build()
402            .unwrap();
403        let message1 = MessageBuilder::new()
404            .id(256)
405            .name("EngineData")
406            .dlc(8)
407            .sender("ECM")
408            .add_signal(signal.clone())
409            .build()
410            .unwrap();
411        let message2 = MessageBuilder::new()
412            .id(512)
413            .name("BrakeData")
414            .dlc(4)
415            .sender("ECM")
416            .add_signal(signal)
417            .build()
418            .unwrap();
419
420        let dbc = DbcBuilder::new()
421            .version(version)
422            .nodes(nodes)
423            .add_messages(vec![message1, message2])
424            .build()
425            .unwrap();
426
427        assert_eq!(dbc.messages().len(), 2);
428    }
429
430    #[test]
431    fn test_dbc_builder_messages() {
432        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
433        let version = Version::parse(&mut parser).unwrap();
434        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
435        let signal = SignalBuilder::new()
436            .name("RPM")
437            .start_bit(0)
438            .length(16)
439            .byte_order(ByteOrder::BigEndian)
440            .unsigned(true)
441            .factor(1.0)
442            .offset(0.0)
443            .min(0.0)
444            .max(100.0)
445            .receivers(ReceiversBuilder::new().none().build().unwrap())
446            .build()
447            .unwrap();
448        let message1 = MessageBuilder::new()
449            .id(256)
450            .name("EngineData")
451            .dlc(8)
452            .sender("ECM")
453            .add_signal(signal.clone())
454            .build()
455            .unwrap();
456        let message2 = MessageBuilder::new()
457            .id(512)
458            .name("BrakeData")
459            .dlc(4)
460            .sender("ECM")
461            .add_signal(signal)
462            .build()
463            .unwrap();
464
465        let dbc = DbcBuilder::new()
466            .version(version)
467            .nodes(nodes)
468            .messages(vec![message1, message2])
469            .build()
470            .unwrap();
471
472        assert_eq!(dbc.messages().len(), 2);
473    }
474
475    #[test]
476    fn test_dbc_builder_clear_messages() {
477        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
478        let version = Version::parse(&mut parser).unwrap();
479        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
480        let signal = SignalBuilder::new()
481            .name("RPM")
482            .start_bit(0)
483            .length(16)
484            .byte_order(ByteOrder::BigEndian)
485            .unsigned(true)
486            .factor(1.0)
487            .offset(0.0)
488            .min(0.0)
489            .max(100.0)
490            .receivers(ReceiversBuilder::new().none().build().unwrap())
491            .build()
492            .unwrap();
493        let message = MessageBuilder::new()
494            .id(256)
495            .name("EngineData")
496            .dlc(8)
497            .sender("ECM")
498            .add_signal(signal)
499            .build()
500            .unwrap();
501
502        let dbc = DbcBuilder::new()
503            .version(version)
504            .nodes(nodes)
505            .add_message(message)
506            .clear_messages()
507            .build()
508            .unwrap();
509
510        assert_eq!(dbc.messages().len(), 0);
511    }
512
513    #[test]
514    fn test_dbc_builder_validate_missing_version() {
515        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
516        let result = DbcBuilder::new().nodes(nodes).validate();
517        assert!(result.is_err());
518        match result.unwrap_err() {
519            Error::Dbc(msg) => assert!(msg.contains(lang::DBC_VERSION_REQUIRED)),
520            _ => panic!("Expected Dbc error"),
521        }
522    }
523
524    #[test]
525    fn test_dbc_builder_validate_missing_nodes() {
526        // Empty nodes are now allowed per DBC spec
527        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
528        let version = Version::parse(&mut parser).unwrap();
529        let result = DbcBuilder::new().version(version).validate();
530        // Validation should succeed with empty nodes
531        assert!(result.is_ok());
532    }
533
534    #[test]
535    fn test_dbc_builder_validate_valid() {
536        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
537        let version = Version::parse(&mut parser).unwrap();
538        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
539        let signal = SignalBuilder::new()
540            .name("RPM")
541            .start_bit(0)
542            .length(16)
543            .byte_order(ByteOrder::BigEndian)
544            .unsigned(true)
545            .factor(1.0)
546            .offset(0.0)
547            .min(0.0)
548            .max(100.0)
549            .receivers(ReceiversBuilder::new().none().build().unwrap())
550            .build()
551            .unwrap();
552        let message = MessageBuilder::new()
553            .id(256)
554            .name("EngineData")
555            .dlc(8)
556            .sender("ECM")
557            .add_signal(signal)
558            .build()
559            .unwrap();
560
561        let result =
562            DbcBuilder::new().version(version).nodes(nodes).add_message(message).validate();
563        assert!(result.is_ok());
564        // Verify we can continue building after validation
565        let validated = result.unwrap();
566        let dbc = validated.build().unwrap();
567        assert_eq!(dbc.messages().len(), 1);
568    }
569}