dbc_rs/dbc/builder/
impls.rs

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