dbc_rs/dbc/
dbc_builder.rs

1#[cfg(any(feature = "alloc", feature = "kernel"))]
2use crate::compat::{Box, Vec};
3use crate::{
4    Dbc, Message, Nodes, Version,
5    error::{Error, Result, messages},
6};
7
8/// Builder for constructing `Dbc` instances programmatically.
9///
10/// This builder allows you to create DBC files without parsing from a string.
11/// It requires the `alloc` feature to be enabled.
12///
13/// # Examples
14///
15/// ```rust,no_run
16/// use dbc_rs::{DbcBuilder, NodesBuilder, MessageBuilder, SignalBuilder, VersionBuilder};
17///
18/// let nodes = NodesBuilder::new()
19///     .add_node("ECM")
20///     .add_node("TCM")
21///     .build()?;
22///
23/// let signal = SignalBuilder::new()
24///     .name("RPM")
25///     .start_bit(0)
26///     .length(16)
27///     .build()?;
28///
29/// let message = MessageBuilder::new()
30///     .id(256)
31///     .name("EngineData")
32///     .dlc(8)
33///     .sender("ECM")
34///     .add_signal(signal)
35///     .build()?;
36///
37/// let dbc = DbcBuilder::new(None)
38///     .version(VersionBuilder::new().version("1.0").build()?)
39///     .nodes(nodes)
40///     .add_message(message)
41///     .build()?;
42/// # Ok::<(), dbc_rs::Error>(())
43/// ```
44#[derive(Debug, Default)]
45pub struct DbcBuilder {
46    version: Option<Version<'static>>,
47    nodes: Option<Nodes<'static>>,
48    messages: Vec<Message<'static>>,
49}
50
51impl DbcBuilder {
52    /// Creates a new `DbcBuilder`.
53    ///
54    /// If a `Dbc` is provided, the builder is initialized with all data from it,
55    /// allowing you to modify an existing DBC file. If `None` is provided, an
56    /// empty builder is created.
57    ///
58    /// # Arguments
59    ///
60    /// * `dbc` - Optional reference to an existing `Dbc` to initialize from
61    ///
62    /// # Examples
63    ///
64    /// ```rust,no_run
65    /// use dbc_rs::DbcBuilder;
66    ///
67    /// // Create empty builder
68    /// let builder = DbcBuilder::new(None);
69    /// ```
70    ///
71    /// ```rust,no_run
72    /// use dbc_rs::{Dbc, DbcBuilder, MessageBuilder};
73    ///
74    /// // Parse existing DBC
75    /// let dbc = Dbc::parse(r#"VERSION "1.0"
76    ///
77    /// BU_: ECM
78    ///
79    /// BO_ 256 Engine : 8 ECM
80    /// "#)?;
81    ///
82    /// // Create builder from existing DBC
83    /// let modified = DbcBuilder::new(Some(&dbc))
84    ///     .add_message(MessageBuilder::new()
85    ///         .id(512)
86    ///         .name("Brake")
87    ///         .dlc(4)
88    ///         .sender("ECM")
89    ///         .build()?)
90    ///     .build()?;
91    ///
92    /// assert_eq!(modified.messages().len(), 2);
93    /// # Ok::<(), dbc_rs::Error>(())
94    /// ```
95    pub fn new(dbc: Option<&Dbc<'_>>) -> Self {
96        match dbc {
97            Some(dbc) => Self::from_dbc(dbc),
98            None => Self::default(),
99        }
100    }
101
102    /// Creates a `DbcBuilder` from an existing `Dbc`.
103    ///
104    /// This is a helper method used internally by `new()`. You can also call it
105    /// directly if you prefer.
106    ///
107    /// # Arguments
108    ///
109    /// * `dbc` - The existing `Dbc` to create a builder from
110    fn from_dbc(dbc: &Dbc<'_>) -> Self {
111        use crate::{
112            MessageBuilder, NodesBuilder, ReceiversBuilder, SignalBuilder, VersionBuilder,
113        };
114
115        // Convert version to 'static using VersionBuilder
116        let version = dbc.version().map(|v| {
117            VersionBuilder::new()
118                .version(v.as_str())
119                .build()
120                .expect("Version conversion should always succeed")
121        });
122
123        // Convert nodes to 'static using NodesBuilder
124        let nodes = {
125            let mut builder = NodesBuilder::new();
126            for node in dbc.nodes().iter() {
127                builder = builder.add_node(node);
128            }
129            builder.build().expect("Nodes conversion should always succeed")
130        };
131
132        // Convert messages to 'static using MessageBuilder and SignalBuilder
133        let messages: Vec<Message<'static>> = dbc
134            .messages()
135            .iter()
136            .map(|msg| {
137                let mut msg_builder = MessageBuilder::new()
138                    .id(msg.id())
139                    .name(msg.name())
140                    .dlc(msg.dlc())
141                    .sender(msg.sender());
142
143                // Convert signals using SignalBuilder
144                for sig in msg.signals().iter() {
145                    let mut sig_builder = SignalBuilder::new()
146                        .name(sig.name())
147                        .start_bit(sig.start_bit())
148                        .length(sig.length())
149                        .byte_order(sig.byte_order())
150                        .unsigned(sig.is_unsigned())
151                        .factor(sig.factor())
152                        .offset(sig.offset())
153                        .min(sig.min())
154                        .max(sig.max());
155
156                    if let Some(unit) = sig.unit() {
157                        sig_builder = sig_builder.unit(unit);
158                    }
159
160                    // Convert receivers using ReceiversBuilder
161                    let receivers = match sig.receivers() {
162                        crate::Receivers::Broadcast => {
163                            ReceiversBuilder::new().broadcast().build().unwrap()
164                        }
165                        crate::Receivers::None => ReceiversBuilder::new().none().build().unwrap(),
166                        crate::Receivers::Nodes(_, _) => {
167                            let mut recv_builder = ReceiversBuilder::new();
168                            for receiver in sig.receivers().iter() {
169                                recv_builder = recv_builder.add_node(receiver);
170                            }
171                            recv_builder.build().unwrap()
172                        }
173                    };
174                    sig_builder = sig_builder.receivers(receivers);
175
176                    msg_builder = msg_builder.add_signal(
177                        sig_builder.build().expect("Signal conversion should always succeed"),
178                    );
179                }
180
181                msg_builder.build().expect("Message conversion should always succeed")
182            })
183            .collect();
184
185        Self {
186            version,
187            nodes: Some(nodes),
188            messages,
189        }
190    }
191
192    /// Sets the version for the DBC file.
193    ///
194    /// # Examples
195    ///
196    /// ```rust,no_run
197    /// use dbc_rs::{DbcBuilder, VersionBuilder};
198    ///
199    /// let builder = DbcBuilder::new(None)
200    ///     .version(VersionBuilder::new().version("1.0").build()?);
201    /// # Ok::<(), dbc_rs::Error>(())
202    /// ```
203    #[must_use]
204    pub fn version(mut self, version: Version<'static>) -> Self {
205        self.version = Some(version);
206        self
207    }
208
209    /// Sets the nodes (ECUs) for the DBC file.
210    ///
211    /// # Examples
212    ///
213    /// ```rust,no_run
214    /// use dbc_rs::{DbcBuilder, NodesBuilder};
215    ///
216    /// let builder = DbcBuilder::new(None)
217    ///     .nodes(NodesBuilder::new().add_node("ECM").build()?);
218    /// # Ok::<(), dbc_rs::Error>(())
219    /// ```
220    #[must_use]
221    pub fn nodes(mut self, nodes: Nodes<'static>) -> Self {
222        self.nodes = Some(nodes);
223        self
224    }
225
226    /// Adds a message to the DBC file.
227    ///
228    /// # Examples
229    ///
230    /// ```rust,no_run
231    /// use dbc_rs::{DbcBuilder, MessageBuilder};
232    ///
233    /// let message = MessageBuilder::new()
234    ///     .id(256)
235    ///     .name("EngineData")
236    ///     .dlc(8)
237    ///     .sender("ECM")
238    ///     .build()?;
239    ///
240    /// let builder = DbcBuilder::new(None)
241    ///     .add_message(message);
242    /// # Ok::<(), dbc_rs::Error>(())
243    /// ```
244    #[must_use]
245    pub fn add_message(mut self, message: Message<'static>) -> Self {
246        self.messages.push(message);
247        self
248    }
249
250    /// Adds multiple messages to the DBC file.
251    ///
252    /// # Examples
253    ///
254    /// ```rust,no_run
255    /// use dbc_rs::{DbcBuilder, MessageBuilder};
256    ///
257    /// let msg1 = MessageBuilder::new().id(256).name("Msg1").dlc(8).sender("ECM").build()?;
258    /// let msg2 = MessageBuilder::new().id(512).name("Msg2").dlc(4).sender("TCM").build()?;
259    ///
260    /// let builder = DbcBuilder::new(None)
261    ///     .add_message(msg1)
262    ///     .add_message(msg2);
263    /// # Ok::<(), dbc_rs::Error>(())
264    /// ```
265    #[must_use]
266    pub fn add_messages(mut self, messages: impl IntoIterator<Item = Message<'static>>) -> Self {
267        self.messages.extend(messages);
268        self
269    }
270
271    /// Sets all messages for the DBC file, replacing any existing messages.
272    ///
273    /// # Examples
274    ///
275    /// ```rust,no_run
276    /// use dbc_rs::{DbcBuilder, MessageBuilder};
277    ///
278    /// let msg = MessageBuilder::new().id(256).name("Msg1").dlc(8).sender("ECM").build()?;
279    ///
280    /// let builder = DbcBuilder::new(None)
281    ///     .add_message(msg);
282    /// # Ok::<(), dbc_rs::Error>(())
283    /// ```
284    #[must_use]
285    pub fn messages(mut self, messages: Vec<Message<'static>>) -> Self {
286        self.messages = messages;
287        self
288    }
289
290    /// Clears all messages from the builder.
291    ///
292    /// # Examples
293    ///
294    /// ```rust,no_run
295    /// use dbc_rs::DbcBuilder;
296    ///
297    /// let builder = DbcBuilder::new(None)
298    ///     .clear_messages();
299    /// ```
300    #[must_use]
301    pub fn clear_messages(mut self) -> Self {
302        self.messages.clear();
303        self
304    }
305
306    fn extract_fields(self) -> Result<(Version<'static>, Nodes<'static>, Vec<Message<'static>>)> {
307        let version = self.version.ok_or_else(|| {
308            Error::Dbc(crate::error::str_to_error_string(
309                messages::DBC_VERSION_REQUIRED,
310            ))
311        })?;
312        // Allow empty nodes (DBC spec allows empty BU_: line)
313        let nodes = self.nodes.unwrap_or_default();
314        Ok((version, nodes, self.messages))
315    }
316
317    /// Validates the builder without constructing the `Dbc`.
318    ///
319    /// This method performs all validation checks but returns the builder
320    /// instead of constructing the `Dbc`. Useful for checking if the builder
321    /// is valid before calling `build()`.
322    ///
323    /// # Examples
324    ///
325    /// ```rust,no_run
326    /// use dbc_rs::DbcBuilder;
327    ///
328    /// let builder = DbcBuilder::new(None);
329    /// if builder.validate().is_err() {
330    ///     // Handle validation error
331    /// }
332    /// ```
333    #[must_use = "validation result should be checked"]
334    pub fn validate(self) -> Result<Self> {
335        let (version, nodes, messages) = self.extract_fields()?;
336        // Convert Vec to Option array for validation (all Some)
337        let messages_options: Vec<Option<Message<'static>>> =
338            messages.into_iter().map(Some).collect();
339        let messages_options_slice: &[Option<Message<'static>>] = &messages_options;
340        Dbc::validate(
341            Some(&version),
342            &nodes,
343            messages_options_slice,
344            messages_options_slice.len(),
345        )
346        .map_err(|e| match e {
347            crate::error::ParseError::Version(msg) => {
348                Error::Dbc(crate::error::str_to_error_string(msg))
349            }
350            _ => Error::from(e),
351        })?;
352        Ok(Self {
353            version: Some(version),
354            nodes: Some(nodes),
355            messages: messages_options.into_iter().map(|opt| opt.unwrap()).collect(),
356        })
357    }
358
359    /// Builds the `Dbc` from the builder.
360    ///
361    /// This method validates all fields and constructs the `Dbc` instance.
362    /// Returns an error if validation fails.
363    ///
364    /// # Examples
365    ///
366    /// ```rust,no_run
367    /// use dbc_rs::{DbcBuilder, VersionBuilder, NodesBuilder};
368    ///
369    /// let dbc = DbcBuilder::new(None)
370    ///     .version(VersionBuilder::new().version("1.0").build()?)
371    ///     .nodes(NodesBuilder::new().add_node("ECM").build()?)
372    ///     .build()?;
373    /// # Ok::<(), dbc_rs::Error>(())
374    /// ```
375    pub fn build(self) -> Result<Dbc<'static>> {
376        let (version, nodes, messages) = self.extract_fields()?;
377        // Convert Vec to Option array for validation (all Some)
378        let messages_options: Vec<Option<Message<'static>>> =
379            messages.into_iter().map(Some).collect();
380        let messages_options_slice: &[Option<Message<'static>>] = &messages_options;
381        // Validate before construction
382        Dbc::validate(
383            Some(&version),
384            &nodes,
385            messages_options_slice,
386            messages_options_slice.len(),
387        )
388        .map_err(|e| match e {
389            crate::error::ParseError::Version(msg) => {
390                Error::Dbc(crate::error::str_to_error_string(msg))
391            }
392            _ => Error::from(e),
393        })?;
394        // Convert Option array back to Vec for slice creation
395        let messages: Vec<Message<'static>> =
396            messages_options.into_iter().map(|opt| opt.unwrap()).collect();
397        // Convert Vec to slice by leaking the boxed slice to get 'static lifetime
398        let messages_boxed: Box<[Message<'static>]> = messages.into_boxed_slice();
399        let messages_slice: &'static [Message<'static>] = Box::leak(messages_boxed);
400        Ok(Dbc::new(Some(version), nodes, messages_slice))
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    #![allow(clippy::float_cmp)]
407    use super::DbcBuilder;
408    use crate::{ByteOrder, Dbc, Error, Parser, Version, error::lang};
409    #[cfg(any(feature = "alloc", feature = "kernel"))]
410    use crate::{MessageBuilder, NodesBuilder, ReceiversBuilder, SignalBuilder};
411    #[cfg(any(feature = "alloc", feature = "kernel"))]
412    use alloc::vec;
413
414    #[test]
415    fn test_dbc_builder_valid() {
416        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
417        let version = Version::parse(&mut parser).unwrap();
418        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
419        let signal = SignalBuilder::new()
420            .name("RPM")
421            .start_bit(0)
422            .length(16)
423            .byte_order(ByteOrder::BigEndian)
424            .unsigned(true)
425            .factor(1.0)
426            .offset(0.0)
427            .min(0.0)
428            .max(100.0)
429            .receivers(ReceiversBuilder::new().none().build().unwrap())
430            .build()
431            .unwrap();
432        let message = MessageBuilder::new()
433            .id(256)
434            .name("EngineData")
435            .dlc(8)
436            .sender("ECM")
437            .add_signal(signal)
438            .build()
439            .unwrap();
440
441        let dbc = DbcBuilder::new(None)
442            .version(version)
443            .nodes(nodes)
444            .add_message(message)
445            .build()
446            .unwrap();
447
448        assert_eq!(dbc.messages().len(), 1);
449        assert_eq!(dbc.messages().at(0).unwrap().id(), 256);
450    }
451
452    #[test]
453    fn test_dbc_builder_missing_version() {
454        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
455        let signal = SignalBuilder::new()
456            .name("RPM")
457            .start_bit(0)
458            .length(16)
459            .byte_order(ByteOrder::BigEndian)
460            .unsigned(true)
461            .factor(1.0)
462            .offset(0.0)
463            .min(0.0)
464            .max(100.0)
465            .receivers(ReceiversBuilder::new().none().build().unwrap())
466            .build()
467            .unwrap();
468        let message = MessageBuilder::new()
469            .id(256)
470            .name("EngineData")
471            .dlc(8)
472            .sender("ECM")
473            .add_signal(signal)
474            .build()
475            .unwrap();
476
477        let result = DbcBuilder::new(None).nodes(nodes).add_message(message).build();
478        assert!(result.is_err());
479        match result.unwrap_err() {
480            Error::Dbc(msg) => assert!(msg.contains(lang::DBC_VERSION_REQUIRED)),
481            _ => panic!("Expected Dbc error"),
482        }
483    }
484
485    #[test]
486    fn test_dbc_builder_missing_nodes() {
487        // Empty nodes are now allowed per DBC spec
488        // When nodes are empty, sender validation is skipped
489        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
490        let version = Version::parse(&mut parser).unwrap();
491        let signal = SignalBuilder::new()
492            .name("RPM")
493            .start_bit(0)
494            .length(16)
495            .byte_order(ByteOrder::BigEndian)
496            .unsigned(true)
497            .factor(1.0)
498            .offset(0.0)
499            .min(0.0)
500            .max(100.0)
501            .receivers(ReceiversBuilder::new().none().build().unwrap())
502            .build()
503            .unwrap();
504        let message = MessageBuilder::new()
505            .id(256)
506            .name("EngineData")
507            .dlc(8)
508            .sender("ECM")
509            .add_signal(signal)
510            .build()
511            .unwrap();
512
513        // Building without nodes should succeed (empty nodes allowed)
514        let result = DbcBuilder::new(None).version(version).add_message(message).build();
515        assert!(result.is_ok());
516        let dbc = result.unwrap();
517        assert!(dbc.nodes().is_empty());
518    }
519
520    #[test]
521    fn test_dbc_builder_add_messages() {
522        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
523        let version = Version::parse(&mut parser).unwrap();
524        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
525        let signal = SignalBuilder::new()
526            .name("RPM")
527            .start_bit(0)
528            .length(16)
529            .byte_order(ByteOrder::BigEndian)
530            .unsigned(true)
531            .factor(1.0)
532            .offset(0.0)
533            .min(0.0)
534            .max(100.0)
535            .receivers(ReceiversBuilder::new().none().build().unwrap())
536            .build()
537            .unwrap();
538        let message1 = MessageBuilder::new()
539            .id(256)
540            .name("EngineData")
541            .dlc(8)
542            .sender("ECM")
543            .add_signal(signal.clone())
544            .build()
545            .unwrap();
546        let message2 = MessageBuilder::new()
547            .id(512)
548            .name("BrakeData")
549            .dlc(4)
550            .sender("ECM")
551            .add_signal(signal)
552            .build()
553            .unwrap();
554
555        let dbc = DbcBuilder::new(None)
556            .version(version)
557            .nodes(nodes)
558            .add_messages(vec![message1, message2])
559            .build()
560            .unwrap();
561
562        assert_eq!(dbc.messages().len(), 2);
563    }
564
565    #[test]
566    fn test_dbc_builder_messages() {
567        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
568        let version = Version::parse(&mut parser).unwrap();
569        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
570        let signal = SignalBuilder::new()
571            .name("RPM")
572            .start_bit(0)
573            .length(16)
574            .byte_order(ByteOrder::BigEndian)
575            .unsigned(true)
576            .factor(1.0)
577            .offset(0.0)
578            .min(0.0)
579            .max(100.0)
580            .receivers(ReceiversBuilder::new().none().build().unwrap())
581            .build()
582            .unwrap();
583        let message1 = MessageBuilder::new()
584            .id(256)
585            .name("EngineData")
586            .dlc(8)
587            .sender("ECM")
588            .add_signal(signal.clone())
589            .build()
590            .unwrap();
591        let message2 = MessageBuilder::new()
592            .id(512)
593            .name("BrakeData")
594            .dlc(4)
595            .sender("ECM")
596            .add_signal(signal)
597            .build()
598            .unwrap();
599
600        let dbc = DbcBuilder::new(None)
601            .version(version)
602            .nodes(nodes)
603            .add_message(message1)
604            .add_message(message2)
605            .build()
606            .unwrap();
607
608        assert_eq!(dbc.messages().len(), 2);
609    }
610
611    #[test]
612    fn test_dbc_builder_clear_messages() {
613        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
614        let version = Version::parse(&mut parser).unwrap();
615        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
616        let signal = SignalBuilder::new()
617            .name("RPM")
618            .start_bit(0)
619            .length(16)
620            .byte_order(ByteOrder::BigEndian)
621            .unsigned(true)
622            .factor(1.0)
623            .offset(0.0)
624            .min(0.0)
625            .max(100.0)
626            .receivers(ReceiversBuilder::new().none().build().unwrap())
627            .build()
628            .unwrap();
629        let message = MessageBuilder::new()
630            .id(256)
631            .name("EngineData")
632            .dlc(8)
633            .sender("ECM")
634            .add_signal(signal)
635            .build()
636            .unwrap();
637
638        let dbc = DbcBuilder::new(None)
639            .version(version)
640            .nodes(nodes)
641            .add_message(message)
642            .clear_messages()
643            .build()
644            .unwrap();
645
646        assert_eq!(dbc.messages().len(), 0);
647    }
648
649    #[test]
650    fn test_dbc_builder_validate_missing_version() {
651        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
652        let result = DbcBuilder::new(None).nodes(nodes).validate();
653        assert!(result.is_err());
654        match result.unwrap_err() {
655            Error::Dbc(msg) => assert!(msg.contains(lang::DBC_VERSION_REQUIRED)),
656            _ => panic!("Expected Dbc error"),
657        }
658    }
659
660    #[test]
661    fn test_dbc_builder_validate_missing_nodes() {
662        // Empty nodes are now allowed per DBC spec
663        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
664        let version = Version::parse(&mut parser).unwrap();
665        let result = DbcBuilder::new(None).version(version).validate();
666        // Validation should succeed with empty nodes
667        assert!(result.is_ok());
668    }
669
670    #[test]
671    fn test_dbc_builder_validate_valid() {
672        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
673        let version = Version::parse(&mut parser).unwrap();
674        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
675        let signal = SignalBuilder::new()
676            .name("RPM")
677            .start_bit(0)
678            .length(16)
679            .byte_order(ByteOrder::BigEndian)
680            .unsigned(true)
681            .factor(1.0)
682            .offset(0.0)
683            .min(0.0)
684            .max(100.0)
685            .receivers(ReceiversBuilder::new().none().build().unwrap())
686            .build()
687            .unwrap();
688        let message = MessageBuilder::new()
689            .id(256)
690            .name("EngineData")
691            .dlc(8)
692            .sender("ECM")
693            .add_signal(signal)
694            .build()
695            .unwrap();
696
697        let result = DbcBuilder::new(None)
698            .version(version)
699            .nodes(nodes)
700            .add_message(message)
701            .validate();
702        assert!(result.is_ok());
703        // Verify we can continue building after validation
704        let validated = result.unwrap();
705        let dbc = validated.build().unwrap();
706        assert_eq!(dbc.messages().len(), 1);
707    }
708
709    #[test]
710    fn test_dbc_builder_from_dbc() {
711        // Parse an existing DBC
712        let dbc_content = r#"VERSION "1.0"
713
714BU_: ECM TCM
715
716BO_ 256 Engine : 8 ECM
717 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
718"#;
719        let original_dbc = Dbc::parse(dbc_content).unwrap();
720
721        // Create builder from existing DBC
722        let modified_dbc = DbcBuilder::new(Some(&original_dbc))
723            .add_message(
724                MessageBuilder::new()
725                    .id(512)
726                    .name("Brake")
727                    .dlc(4)
728                    .sender("TCM")
729                    .build()
730                    .unwrap(),
731            )
732            .build()
733            .unwrap();
734
735        // Verify original data is preserved
736        assert_eq!(modified_dbc.version().map(|v| v.as_str()), Some("1.0"));
737        assert_eq!(modified_dbc.nodes().len(), 2);
738        assert!(modified_dbc.nodes().contains("ECM"));
739        assert!(modified_dbc.nodes().contains("TCM"));
740
741        // Verify original message is present
742        assert_eq!(modified_dbc.messages().len(), 2);
743        assert!(modified_dbc.messages().iter().any(|m| m.id() == 256));
744        assert!(modified_dbc.messages().iter().any(|m| m.id() == 512));
745
746        // Verify original message's signal is preserved
747        let engine_msg = modified_dbc.messages().iter().find(|m| m.id() == 256).unwrap();
748        assert_eq!(engine_msg.signals().len(), 1);
749        assert_eq!(engine_msg.signals().at(0).unwrap().name(), "RPM");
750    }
751
752    #[test]
753    fn test_dbc_builder_from_dbc_empty() {
754        // Parse a minimal DBC
755        let dbc_content = r#"VERSION "1.0"
756
757BU_:
758"#;
759        let original_dbc = Dbc::parse(dbc_content).unwrap();
760
761        // Create builder from existing DBC
762        let modified_dbc = DbcBuilder::new(Some(&original_dbc))
763            .add_message(
764                MessageBuilder::new().id(256).name("Test").dlc(8).sender("ECM").build().unwrap(),
765            )
766            .build()
767            .unwrap();
768
769        // Verify version is preserved
770        assert_eq!(modified_dbc.version().map(|v| v.as_str()), Some("1.0"));
771        // Empty nodes are preserved
772        assert!(modified_dbc.nodes().is_empty());
773        // New message is added
774        assert_eq!(modified_dbc.messages().len(), 1);
775    }
776}