dbc_rs/dbc/
dbc_builder.rs

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