dbc_rs/dbc/builder/
build.rs

1use super::DbcBuilder;
2use crate::{
3    BitTiming, Dbc, ExtendedMultiplexing, MAX_EXTENDED_MULTIPLEXING, MAX_MESSAGES, MAX_NAME_SIZE,
4    Message, Nodes, Result, Version,
5    compat::{BTreeMap, String, Vec as CompatVec},
6    dbc::{Messages, Validate},
7};
8
9impl DbcBuilder {
10    /// Validates the builder without constructing the `Dbc`.
11    ///
12    /// This method performs all validation checks. Note that this consumes
13    /// the builder. If you want to keep the builder after validation, call
14    /// `build()` instead and check the result.
15    ///
16    /// # Examples
17    ///
18    /// ```rust,no_run
19    /// use dbc_rs::DbcBuilder;
20    ///
21    /// let builder = DbcBuilder::new();
22    /// if builder.validate().is_err() {
23    ///     // Handle validation error
24    /// }
25    /// ```
26    #[must_use = "validation result should be checked"]
27    pub fn validate(self) -> Result<()> {
28        // Build and validate (extract_fields builds everything)
29        // We need to call extract_fields from the impl<'a> block
30        // Since validate doesn't need the lifetime, we can just build and drop
31        let (_version, nodes, messages, value_descriptions, extended_multiplexing) = {
32            let version = self.version.build()?;
33            let nodes = self.nodes.build()?;
34            let messages: std::vec::Vec<Message> = self
35                .messages
36                .into_iter()
37                .map(|builder| builder.build())
38                .collect::<Result<std::vec::Vec<_>>>()?;
39            let mut value_descriptions_map: BTreeMap<
40                (Option<u32>, String<{ MAX_NAME_SIZE }>),
41                crate::value_descriptions::ValueDescriptions,
42                { MAX_MESSAGES },
43            > = BTreeMap::new();
44            for ((message_id, signal_name), vd_builder) in self.value_descriptions {
45                let vd: crate::value_descriptions::ValueDescriptions = vd_builder.build()?;
46                let compat_signal_name = String::try_from(signal_name.as_str())
47                    .map_err(|_| crate::Error::Validation(crate::Error::MAX_NAME_SIZE_EXCEEDED))?;
48                let _ = value_descriptions_map.insert((message_id, compat_signal_name), vd);
49            }
50            let value_descriptions = crate::dbc::ValueDescriptionsMap::new(value_descriptions_map);
51
52            // Build extended multiplexing entries
53            let extended_multiplexing_vec: std::vec::Vec<ExtendedMultiplexing> = self
54                .extended_multiplexing
55                .into_iter()
56                .map(|builder| builder.build())
57                .collect::<Result<std::vec::Vec<_>>>()?;
58
59            (
60                version,
61                nodes,
62                messages,
63                value_descriptions,
64                extended_multiplexing_vec,
65            )
66        };
67
68        // Validate messages and extended multiplexing
69        Validate::validate(
70            &nodes,
71            &messages,
72            Some(&value_descriptions),
73            Some(&extended_multiplexing),
74        )?;
75
76        Ok(())
77    }
78
79    #[allow(clippy::type_complexity)]
80    fn extract_fields(
81        self,
82    ) -> Result<(
83        Version,
84        Option<BitTiming>,
85        Nodes,
86        Messages,
87        crate::dbc::ValueDescriptionsMap,
88        CompatVec<ExtendedMultiplexing, { MAX_EXTENDED_MULTIPLEXING }>,
89        Option<std::string::String>,
90    )> {
91        // Build version
92        let version = self.version.build()?;
93
94        // Build bit timing if present
95        let bit_timing = match self.bit_timing {
96            Some(builder) => {
97                let bt = builder.build()?;
98                if bt.is_empty() { None } else { Some(bt) }
99            }
100            None => None,
101        };
102
103        // Build nodes (allow empty - DBC spec allows empty BU_: line)
104        let nodes = self.nodes.build()?;
105
106        // Build messages
107        // Collect into a temporary Vec first, then convert to slice for Messages::new
108        let messages_vec: std::vec::Vec<Message> = self
109            .messages
110            .into_iter()
111            .map(|builder| builder.build())
112            .collect::<Result<std::vec::Vec<_>>>()?;
113        let messages = Messages::new(&messages_vec)?;
114
115        // Build value descriptions
116        let mut value_descriptions_map: BTreeMap<
117            (Option<u32>, String<{ MAX_NAME_SIZE }>),
118            crate::value_descriptions::ValueDescriptions,
119            { MAX_MESSAGES },
120        > = BTreeMap::new();
121        for ((message_id, signal_name), vd_builder) in self.value_descriptions {
122            let vd: crate::value_descriptions::ValueDescriptions = vd_builder.build()?;
123            let compat_signal_name = String::try_from(signal_name.as_str())
124                .map_err(|_| crate::Error::Validation(crate::Error::MAX_NAME_SIZE_EXCEEDED))?;
125            let _ = value_descriptions_map.insert((message_id, compat_signal_name), vd);
126        }
127        let value_descriptions = crate::dbc::ValueDescriptionsMap::new(value_descriptions_map);
128
129        // Build extended multiplexing entries
130        let extended_multiplexing_vec: std::vec::Vec<ExtendedMultiplexing> = self
131            .extended_multiplexing
132            .into_iter()
133            .map(|builder| builder.build())
134            .collect::<Result<std::vec::Vec<_>>>()?;
135
136        // Convert to compat Vec
137        let mut extended_multiplexing: CompatVec<
138            ExtendedMultiplexing,
139            { MAX_EXTENDED_MULTIPLEXING },
140        > = CompatVec::new();
141        for ext_mux in extended_multiplexing_vec {
142            extended_multiplexing
143                .push(ext_mux)
144                .map_err(|_| crate::Error::expected("too many extended multiplexing entries"))?;
145        }
146
147        Ok((
148            version,
149            bit_timing,
150            nodes,
151            messages,
152            value_descriptions,
153            extended_multiplexing,
154            self.comment,
155        ))
156    }
157
158    /// Builds the `Dbc` from the builder.
159    ///
160    /// This method validates all fields and constructs the `Dbc` instance.
161    /// Returns an error if validation fails.
162    ///
163    /// # Examples
164    ///
165    /// ```rust,no_run
166    /// use dbc_rs::{DbcBuilder, VersionBuilder, NodesBuilder};
167    ///
168    /// let dbc = DbcBuilder::new()
169    ///     .version(VersionBuilder::new().version("1.0"))
170    ///     .nodes(NodesBuilder::new().add_node("ECM"))
171    ///     .build()?;
172    /// # Ok::<(), dbc_rs::Error>(())
173    /// ```
174    pub fn build(self) -> Result<Dbc> {
175        let (
176            version,
177            bit_timing,
178            nodes,
179            messages,
180            value_descriptions,
181            extended_multiplexing,
182            comment,
183        ) = self.extract_fields()?;
184        // Validate before construction
185        // Get slice from Messages for validation
186        let messages_slice: std::vec::Vec<Message> = messages.iter().cloned().collect();
187        let extended_multiplexing_slice: std::vec::Vec<ExtendedMultiplexing> =
188            extended_multiplexing.iter().cloned().collect();
189        Validate::validate(
190            &nodes,
191            &messages_slice,
192            Some(&value_descriptions),
193            Some(&extended_multiplexing_slice),
194        )?;
195        Ok(Dbc::new(
196            Some(version),
197            bit_timing,
198            nodes,
199            messages,
200            value_descriptions,
201            extended_multiplexing,
202            comment.map(|c| c.into()),
203        ))
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    #![allow(clippy::float_cmp)]
210    use super::DbcBuilder;
211    use crate::{
212        ByteOrder, ExtendedMultiplexingBuilder, MessageBuilder, NodesBuilder, ReceiversBuilder,
213        SignalBuilder, ValueDescriptionsBuilder, VersionBuilder,
214    };
215
216    #[test]
217    fn test_dbc_builder_valid() {
218        let version = VersionBuilder::new().version("1.0");
219        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
220        let signal = SignalBuilder::new()
221            .name("RPM")
222            .start_bit(0)
223            .length(16)
224            .byte_order(ByteOrder::BigEndian)
225            .unsigned(true)
226            .factor(1.0)
227            .offset(0.0)
228            .min(0.0)
229            .max(100.0)
230            .receivers(ReceiversBuilder::new().none());
231        let message = MessageBuilder::new()
232            .id(256)
233            .name("EngineData")
234            .dlc(8)
235            .sender("ECM")
236            .add_signal(signal);
237
238        let dbc = DbcBuilder::new()
239            .version(version)
240            .nodes(nodes)
241            .add_message(message)
242            .build()
243            .unwrap();
244
245        assert_eq!(dbc.messages().len(), 1);
246        assert_eq!(dbc.messages().at(0).unwrap().id(), 256);
247    }
248
249    #[test]
250    fn test_dbc_builder_missing_version() {
251        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
252        let signal = SignalBuilder::new()
253            .name("RPM")
254            .start_bit(0)
255            .length(16)
256            .byte_order(ByteOrder::BigEndian)
257            .unsigned(true)
258            .factor(1.0)
259            .offset(0.0)
260            .min(0.0)
261            .max(100.0)
262            .receivers(ReceiversBuilder::new().none());
263        let message = MessageBuilder::new()
264            .id(256)
265            .name("EngineData")
266            .dlc(8)
267            .sender("ECM")
268            .add_signal(signal);
269
270        let result = DbcBuilder::new().nodes(nodes).add_message(message).build();
271        // VersionBuilder now allows empty version, so this should succeed
272        assert!(result.is_ok());
273        let dbc = result.unwrap();
274        assert_eq!(dbc.version().unwrap().as_str(), "");
275    }
276
277    #[test]
278    fn test_dbc_builder_missing_nodes() {
279        // Empty nodes are now allowed per DBC spec
280        // When nodes are empty, sender validation is skipped
281        let version = VersionBuilder::new().version("1.0");
282        let signal = SignalBuilder::new()
283            .name("RPM")
284            .start_bit(0)
285            .length(16)
286            .byte_order(ByteOrder::BigEndian)
287            .unsigned(true)
288            .factor(1.0)
289            .offset(0.0)
290            .min(0.0)
291            .max(100.0)
292            .receivers(ReceiversBuilder::new().none());
293        let message = MessageBuilder::new()
294            .id(256)
295            .name("EngineData")
296            .dlc(8)
297            .sender("ECM")
298            .add_signal(signal);
299
300        // Building without nodes should succeed (empty nodes allowed)
301        let result = DbcBuilder::new().version(version).add_message(message).build();
302        assert!(result.is_ok());
303        let dbc = result.unwrap();
304        assert!(dbc.nodes().is_empty());
305    }
306
307    #[test]
308    fn test_dbc_builder_validate_missing_version() {
309        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
310        // VersionBuilder now allows empty version, so validation should succeed
311        let result = DbcBuilder::new().nodes(nodes).validate();
312        assert!(result.is_ok());
313    }
314
315    #[test]
316    fn test_dbc_builder_validate_missing_nodes() {
317        // Empty nodes are now allowed per DBC spec
318        let version = VersionBuilder::new().version("1.0");
319        let result = DbcBuilder::new().version(version).validate();
320        // Validation should succeed with empty nodes
321        assert!(result.is_ok());
322    }
323
324    #[test]
325    fn test_dbc_builder_validate_valid() {
326        let version = VersionBuilder::new().version("1.0");
327        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
328        let signal = SignalBuilder::new()
329            .name("RPM")
330            .start_bit(0)
331            .length(16)
332            .byte_order(ByteOrder::BigEndian)
333            .unsigned(true)
334            .factor(1.0)
335            .offset(0.0)
336            .min(0.0)
337            .max(100.0)
338            .receivers(ReceiversBuilder::new().none());
339        let message = MessageBuilder::new()
340            .id(256)
341            .name("EngineData")
342            .dlc(8)
343            .sender("ECM")
344            .add_signal(signal);
345
346        // validate() consumes the builder, so we can't use it after
347        // But we can check it doesn't error
348        let builder = DbcBuilder::new().version(version).nodes(nodes).add_message(message);
349        let result = builder.validate();
350        assert!(result.is_ok());
351    }
352
353    // ========================================================================
354    // Value Descriptions Tests
355    // ========================================================================
356
357    #[test]
358    fn test_dbc_builder_with_value_descriptions() {
359        let version = VersionBuilder::new().version("1.0");
360        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
361        let signal = SignalBuilder::new()
362            .name("Gear")
363            .start_bit(0)
364            .length(8)
365            .byte_order(ByteOrder::LittleEndian)
366            .unsigned(true)
367            .factor(1.0)
368            .offset(0.0)
369            .min(0.0)
370            .max(5.0)
371            .receivers(ReceiversBuilder::new().none());
372        let message = MessageBuilder::new()
373            .id(256)
374            .name("Transmission")
375            .dlc(8)
376            .sender("ECM")
377            .add_signal(signal);
378
379        let value_desc = ValueDescriptionsBuilder::new()
380            .add_entry(0, "Park")
381            .add_entry(1, "Reverse")
382            .add_entry(2, "Neutral")
383            .add_entry(3, "Drive");
384
385        let dbc = DbcBuilder::new()
386            .version(version)
387            .nodes(nodes)
388            .add_message(message)
389            .add_value_description(256, "Gear", value_desc)
390            .build()
391            .unwrap();
392
393        // Verify value descriptions are present
394        let vd = dbc.value_descriptions_for_signal(256, "Gear").unwrap();
395        assert_eq!(vd.get(0), Some("Park"));
396        assert_eq!(vd.get(1), Some("Reverse"));
397        assert_eq!(vd.get(2), Some("Neutral"));
398        assert_eq!(vd.get(3), Some("Drive"));
399    }
400
401    #[test]
402    fn test_dbc_builder_value_descriptions_message_not_found() {
403        let version = VersionBuilder::new().version("1.0");
404        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
405        let signal = SignalBuilder::new()
406            .name("RPM")
407            .start_bit(0)
408            .length(16)
409            .byte_order(ByteOrder::BigEndian)
410            .unsigned(true)
411            .factor(1.0)
412            .offset(0.0)
413            .min(0.0)
414            .max(100.0)
415            .receivers(ReceiversBuilder::new().none());
416        let message = MessageBuilder::new()
417            .id(256)
418            .name("EngineData")
419            .dlc(8)
420            .sender("ECM")
421            .add_signal(signal);
422
423        // Add value description for non-existent message
424        let value_desc = ValueDescriptionsBuilder::new().add_entry(0, "Off").add_entry(1, "On");
425
426        let result = DbcBuilder::new()
427            .version(version)
428            .nodes(nodes)
429            .add_message(message)
430            .add_value_description(999, "RPM", value_desc) // Message 999 doesn't exist
431            .build();
432
433        assert!(result.is_err());
434    }
435
436    #[test]
437    fn test_dbc_builder_value_descriptions_signal_not_found() {
438        let version = VersionBuilder::new().version("1.0");
439        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
440        let signal = SignalBuilder::new()
441            .name("RPM")
442            .start_bit(0)
443            .length(16)
444            .byte_order(ByteOrder::BigEndian)
445            .unsigned(true)
446            .factor(1.0)
447            .offset(0.0)
448            .min(0.0)
449            .max(100.0)
450            .receivers(ReceiversBuilder::new().none());
451        let message = MessageBuilder::new()
452            .id(256)
453            .name("EngineData")
454            .dlc(8)
455            .sender("ECM")
456            .add_signal(signal);
457
458        // Add value description for non-existent signal
459        let value_desc = ValueDescriptionsBuilder::new().add_entry(0, "Off").add_entry(1, "On");
460
461        let result = DbcBuilder::new()
462            .version(version)
463            .nodes(nodes)
464            .add_message(message)
465            .add_value_description(256, "NonExistentSignal", value_desc)
466            .build();
467
468        assert!(result.is_err());
469    }
470
471    #[test]
472    fn test_dbc_builder_validate_with_value_descriptions() {
473        let version = VersionBuilder::new().version("1.0");
474        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
475        let signal = SignalBuilder::new()
476            .name("Gear")
477            .start_bit(0)
478            .length(8)
479            .byte_order(ByteOrder::LittleEndian)
480            .unsigned(true)
481            .factor(1.0)
482            .offset(0.0)
483            .min(0.0)
484            .max(5.0)
485            .receivers(ReceiversBuilder::new().none());
486        let message = MessageBuilder::new()
487            .id(256)
488            .name("Transmission")
489            .dlc(8)
490            .sender("ECM")
491            .add_signal(signal);
492
493        let value_desc = ValueDescriptionsBuilder::new().add_entry(0, "Park").add_entry(1, "Drive");
494
495        let result = DbcBuilder::new()
496            .version(version)
497            .nodes(nodes)
498            .add_message(message)
499            .add_value_description(256, "Gear", value_desc)
500            .validate();
501
502        assert!(result.is_ok());
503    }
504
505    // ========================================================================
506    // Extended Multiplexing Tests
507    // ========================================================================
508
509    #[test]
510    fn test_dbc_builder_with_extended_multiplexing() {
511        let version = VersionBuilder::new().version("1.0");
512        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
513
514        // Create a multiplexer signal
515        let mux_signal = SignalBuilder::new()
516            .name("Mux1")
517            .start_bit(0)
518            .length(8)
519            .byte_order(ByteOrder::LittleEndian)
520            .unsigned(true)
521            .factor(1.0)
522            .offset(0.0)
523            .min(0.0)
524            .max(255.0)
525            .receivers(ReceiversBuilder::new().none());
526
527        // Create a multiplexed signal
528        let signal_a = SignalBuilder::new()
529            .name("SignalA")
530            .start_bit(16)
531            .length(16)
532            .byte_order(ByteOrder::LittleEndian)
533            .unsigned(true)
534            .factor(0.1)
535            .offset(0.0)
536            .min(0.0)
537            .max(100.0)
538            .receivers(ReceiversBuilder::new().none());
539
540        let message = MessageBuilder::new()
541            .id(500)
542            .name("MuxMessage")
543            .dlc(8)
544            .sender("ECM")
545            .add_signal(mux_signal)
546            .add_signal(signal_a);
547
548        // Create extended multiplexing entry
549        let ext_mux = ExtendedMultiplexingBuilder::new()
550            .message_id(500)
551            .signal_name("SignalA")
552            .multiplexer_switch("Mux1")
553            .add_value_range(0, 5)
554            .add_value_range(10, 15);
555
556        let dbc = DbcBuilder::new()
557            .version(version)
558            .nodes(nodes)
559            .add_message(message)
560            .add_extended_multiplexing(ext_mux)
561            .build()
562            .unwrap();
563
564        assert_eq!(dbc.messages().len(), 1);
565        assert_eq!(dbc.extended_multiplexing().len(), 1);
566
567        let ext_mux = &dbc.extended_multiplexing()[0];
568        assert_eq!(ext_mux.message_id(), 500);
569        assert_eq!(ext_mux.signal_name(), "SignalA");
570        assert_eq!(ext_mux.multiplexer_switch(), "Mux1");
571        assert_eq!(ext_mux.value_ranges().len(), 2);
572        assert_eq!(ext_mux.value_ranges()[0], (0, 5));
573        assert_eq!(ext_mux.value_ranges()[1], (10, 15));
574    }
575
576    #[test]
577    fn test_dbc_builder_extended_multiplexing_message_not_found() {
578        let version = VersionBuilder::new().version("1.0");
579        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
580
581        let signal = SignalBuilder::new()
582            .name("RPM")
583            .start_bit(0)
584            .length(16)
585            .byte_order(ByteOrder::BigEndian)
586            .unsigned(true)
587            .factor(1.0)
588            .offset(0.0)
589            .min(0.0)
590            .max(100.0)
591            .receivers(ReceiversBuilder::new().none());
592
593        let message = MessageBuilder::new()
594            .id(256)
595            .name("EngineData")
596            .dlc(8)
597            .sender("ECM")
598            .add_signal(signal);
599
600        // Create extended multiplexing referencing non-existent message
601        let ext_mux = ExtendedMultiplexingBuilder::new()
602            .message_id(999) // Non-existent message
603            .signal_name("RPM")
604            .multiplexer_switch("Mux1")
605            .add_value_range(0, 5);
606
607        let result = DbcBuilder::new()
608            .version(version)
609            .nodes(nodes)
610            .add_message(message)
611            .add_extended_multiplexing(ext_mux)
612            .build();
613
614        assert!(result.is_err());
615    }
616
617    #[test]
618    fn test_dbc_builder_extended_multiplexing_signal_not_found() {
619        let version = VersionBuilder::new().version("1.0");
620        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
621
622        let mux_signal = SignalBuilder::new()
623            .name("Mux1")
624            .start_bit(0)
625            .length(8)
626            .byte_order(ByteOrder::BigEndian)
627            .unsigned(true)
628            .factor(1.0)
629            .offset(0.0)
630            .min(0.0)
631            .max(100.0)
632            .receivers(ReceiversBuilder::new().none());
633
634        let message = MessageBuilder::new()
635            .id(256)
636            .name("EngineData")
637            .dlc(8)
638            .sender("ECM")
639            .add_signal(mux_signal);
640
641        // Create extended multiplexing referencing non-existent signal
642        let ext_mux = ExtendedMultiplexingBuilder::new()
643            .message_id(256)
644            .signal_name("NonExistent") // Non-existent signal
645            .multiplexer_switch("Mux1")
646            .add_value_range(0, 5);
647
648        let result = DbcBuilder::new()
649            .version(version)
650            .nodes(nodes)
651            .add_message(message)
652            .add_extended_multiplexing(ext_mux)
653            .build();
654
655        assert!(result.is_err());
656    }
657
658    #[test]
659    fn test_dbc_builder_extended_multiplexing_switch_not_found() {
660        let version = VersionBuilder::new().version("1.0");
661        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
662
663        let signal = SignalBuilder::new()
664            .name("SignalA")
665            .start_bit(0)
666            .length(16)
667            .byte_order(ByteOrder::BigEndian)
668            .unsigned(true)
669            .factor(1.0)
670            .offset(0.0)
671            .min(0.0)
672            .max(100.0)
673            .receivers(ReceiversBuilder::new().none());
674
675        let message = MessageBuilder::new()
676            .id(256)
677            .name("EngineData")
678            .dlc(8)
679            .sender("ECM")
680            .add_signal(signal);
681
682        // Create extended multiplexing referencing non-existent multiplexer switch
683        let ext_mux = ExtendedMultiplexingBuilder::new()
684            .message_id(256)
685            .signal_name("SignalA")
686            .multiplexer_switch("NonExistentMux") // Non-existent switch
687            .add_value_range(0, 5);
688
689        let result = DbcBuilder::new()
690            .version(version)
691            .nodes(nodes)
692            .add_message(message)
693            .add_extended_multiplexing(ext_mux)
694            .build();
695
696        assert!(result.is_err());
697    }
698
699    #[test]
700    fn test_dbc_builder_extended_multiplexing_invalid_range() {
701        let version = VersionBuilder::new().version("1.0");
702        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
703
704        let mux_signal = SignalBuilder::new()
705            .name("Mux1")
706            .start_bit(0)
707            .length(8)
708            .byte_order(ByteOrder::LittleEndian)
709            .unsigned(true)
710            .factor(1.0)
711            .offset(0.0)
712            .min(0.0)
713            .max(255.0)
714            .receivers(ReceiversBuilder::new().none());
715
716        let signal_a = SignalBuilder::new()
717            .name("SignalA")
718            .start_bit(16)
719            .length(16)
720            .byte_order(ByteOrder::LittleEndian)
721            .unsigned(true)
722            .factor(0.1)
723            .offset(0.0)
724            .min(0.0)
725            .max(100.0)
726            .receivers(ReceiversBuilder::new().none());
727
728        let message = MessageBuilder::new()
729            .id(500)
730            .name("MuxMessage")
731            .dlc(8)
732            .sender("ECM")
733            .add_signal(mux_signal)
734            .add_signal(signal_a);
735
736        // Create extended multiplexing with invalid range (min > max)
737        let ext_mux = ExtendedMultiplexingBuilder::new()
738            .message_id(500)
739            .signal_name("SignalA")
740            .multiplexer_switch("Mux1")
741            .add_value_range(10, 5); // Invalid: min > max
742
743        let result = DbcBuilder::new()
744            .version(version)
745            .nodes(nodes)
746            .add_message(message)
747            .add_extended_multiplexing(ext_mux)
748            .build();
749
750        assert!(result.is_err());
751    }
752
753    #[test]
754    fn test_dbc_builder_validate_with_extended_multiplexing() {
755        let version = VersionBuilder::new().version("1.0");
756        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
757
758        let mux_signal = SignalBuilder::new()
759            .name("Mux1")
760            .start_bit(0)
761            .length(8)
762            .byte_order(ByteOrder::LittleEndian)
763            .unsigned(true)
764            .factor(1.0)
765            .offset(0.0)
766            .min(0.0)
767            .max(255.0)
768            .receivers(ReceiversBuilder::new().none());
769
770        let signal_a = SignalBuilder::new()
771            .name("SignalA")
772            .start_bit(16)
773            .length(16)
774            .byte_order(ByteOrder::LittleEndian)
775            .unsigned(true)
776            .factor(0.1)
777            .offset(0.0)
778            .min(0.0)
779            .max(100.0)
780            .receivers(ReceiversBuilder::new().none());
781
782        let message = MessageBuilder::new()
783            .id(500)
784            .name("MuxMessage")
785            .dlc(8)
786            .sender("ECM")
787            .add_signal(mux_signal)
788            .add_signal(signal_a);
789
790        let ext_mux = ExtendedMultiplexingBuilder::new()
791            .message_id(500)
792            .signal_name("SignalA")
793            .multiplexer_switch("Mux1")
794            .add_value_range(0, 5);
795
796        let result = DbcBuilder::new()
797            .version(version)
798            .nodes(nodes)
799            .add_message(message)
800            .add_extended_multiplexing(ext_mux)
801            .validate();
802
803        assert!(result.is_ok());
804    }
805
806    #[test]
807    fn test_dbc_builder_multiple_extended_multiplexing() {
808        let version = VersionBuilder::new().version("1.0");
809        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
810
811        let mux_signal = SignalBuilder::new()
812            .name("Mux1")
813            .start_bit(0)
814            .length(8)
815            .byte_order(ByteOrder::LittleEndian)
816            .unsigned(true)
817            .factor(1.0)
818            .offset(0.0)
819            .min(0.0)
820            .max(255.0)
821            .receivers(ReceiversBuilder::new().none());
822
823        let signal_a = SignalBuilder::new()
824            .name("SignalA")
825            .start_bit(16)
826            .length(8)
827            .byte_order(ByteOrder::LittleEndian)
828            .unsigned(true)
829            .factor(1.0)
830            .offset(0.0)
831            .min(0.0)
832            .max(255.0)
833            .receivers(ReceiversBuilder::new().none());
834
835        let signal_b = SignalBuilder::new()
836            .name("SignalB")
837            .start_bit(24)
838            .length(8)
839            .byte_order(ByteOrder::LittleEndian)
840            .unsigned(true)
841            .factor(1.0)
842            .offset(0.0)
843            .min(0.0)
844            .max(255.0)
845            .receivers(ReceiversBuilder::new().none());
846
847        let message = MessageBuilder::new()
848            .id(500)
849            .name("MuxMessage")
850            .dlc(8)
851            .sender("ECM")
852            .add_signal(mux_signal)
853            .add_signal(signal_a)
854            .add_signal(signal_b);
855
856        let ext_mux_a = ExtendedMultiplexingBuilder::new()
857            .message_id(500)
858            .signal_name("SignalA")
859            .multiplexer_switch("Mux1")
860            .add_value_range(0, 5);
861
862        let ext_mux_b = ExtendedMultiplexingBuilder::new()
863            .message_id(500)
864            .signal_name("SignalB")
865            .multiplexer_switch("Mux1")
866            .add_value_range(10, 15);
867
868        let dbc = DbcBuilder::new()
869            .version(version)
870            .nodes(nodes)
871            .add_message(message)
872            .add_extended_multiplexings(vec![ext_mux_a, ext_mux_b])
873            .build()
874            .unwrap();
875
876        assert_eq!(dbc.extended_multiplexing().len(), 2);
877        assert_eq!(dbc.extended_multiplexing()[0].signal_name(), "SignalA");
878        assert_eq!(dbc.extended_multiplexing()[1].signal_name(), "SignalB");
879    }
880
881    #[test]
882    fn test_dbc_builder_clear_extended_multiplexing() {
883        let version = VersionBuilder::new().version("1.0");
884        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
885
886        let mux_signal = SignalBuilder::new()
887            .name("Mux1")
888            .start_bit(0)
889            .length(8)
890            .byte_order(ByteOrder::LittleEndian)
891            .unsigned(true)
892            .factor(1.0)
893            .offset(0.0)
894            .min(0.0)
895            .max(255.0)
896            .receivers(ReceiversBuilder::new().none());
897
898        let signal_a = SignalBuilder::new()
899            .name("SignalA")
900            .start_bit(16)
901            .length(16)
902            .byte_order(ByteOrder::LittleEndian)
903            .unsigned(true)
904            .factor(0.1)
905            .offset(0.0)
906            .min(0.0)
907            .max(100.0)
908            .receivers(ReceiversBuilder::new().none());
909
910        let message = MessageBuilder::new()
911            .id(500)
912            .name("MuxMessage")
913            .dlc(8)
914            .sender("ECM")
915            .add_signal(mux_signal)
916            .add_signal(signal_a);
917
918        let ext_mux = ExtendedMultiplexingBuilder::new()
919            .message_id(500)
920            .signal_name("SignalA")
921            .multiplexer_switch("Mux1")
922            .add_value_range(0, 5);
923
924        let dbc = DbcBuilder::new()
925            .version(version)
926            .nodes(nodes)
927            .add_message(message)
928            .add_extended_multiplexing(ext_mux)
929            .clear_extended_multiplexing()
930            .build()
931            .unwrap();
932
933        assert_eq!(dbc.extended_multiplexing().len(), 0);
934    }
935
936    // ========================================================================
937    // Combined Tests (Value Descriptions + Extended Multiplexing)
938    // ========================================================================
939
940    #[test]
941    fn test_dbc_builder_with_value_descriptions_and_extended_multiplexing() {
942        let version = VersionBuilder::new().version("1.0");
943        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
944
945        let mux_signal = SignalBuilder::new()
946            .name("Mux1")
947            .start_bit(0)
948            .length(8)
949            .byte_order(ByteOrder::LittleEndian)
950            .unsigned(true)
951            .factor(1.0)
952            .offset(0.0)
953            .min(0.0)
954            .max(255.0)
955            .receivers(ReceiversBuilder::new().none());
956
957        let signal_a = SignalBuilder::new()
958            .name("SignalA")
959            .start_bit(16)
960            .length(8)
961            .byte_order(ByteOrder::LittleEndian)
962            .unsigned(true)
963            .factor(1.0)
964            .offset(0.0)
965            .min(0.0)
966            .max(3.0)
967            .receivers(ReceiversBuilder::new().none());
968
969        let message = MessageBuilder::new()
970            .id(500)
971            .name("MuxMessage")
972            .dlc(8)
973            .sender("ECM")
974            .add_signal(mux_signal)
975            .add_signal(signal_a);
976
977        let value_desc = ValueDescriptionsBuilder::new()
978            .add_entry(0, "Off")
979            .add_entry(1, "Low")
980            .add_entry(2, "Medium")
981            .add_entry(3, "High");
982
983        let ext_mux = ExtendedMultiplexingBuilder::new()
984            .message_id(500)
985            .signal_name("SignalA")
986            .multiplexer_switch("Mux1")
987            .add_value_range(0, 5);
988
989        let dbc = DbcBuilder::new()
990            .version(version)
991            .nodes(nodes)
992            .add_message(message)
993            .add_value_description(500, "SignalA", value_desc)
994            .add_extended_multiplexing(ext_mux)
995            .build()
996            .unwrap();
997
998        // Verify value descriptions
999        let vd = dbc.value_descriptions_for_signal(500, "SignalA").unwrap();
1000        assert_eq!(vd.get(0), Some("Off"));
1001        assert_eq!(vd.get(3), Some("High"));
1002
1003        // Verify extended multiplexing
1004        assert_eq!(dbc.extended_multiplexing().len(), 1);
1005        assert_eq!(dbc.extended_multiplexing()[0].signal_name(), "SignalA");
1006    }
1007}