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}