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