dbc_rs/dbc/builder/
build.rs

1use super::DbcBuilder;
2use crate::{
3    Dbc, ExtendedMultiplexing, MAX_EXTENDED_MULTIPLEXING, Message, Nodes, Result, Version,
4    dbc::{Messages, Validate},
5};
6use std::collections::BTreeMap;
7
8impl DbcBuilder {
9    /// Validates the builder without constructing the `Dbc`.
10    ///
11    /// This method performs all validation checks. Note that this consumes
12    /// the builder. If you want to keep the builder after validation, call
13    /// `build()` instead and check the result.
14    ///
15    /// # Examples
16    ///
17    /// ```rust,no_run
18    /// use dbc_rs::DbcBuilder;
19    ///
20    /// let builder = DbcBuilder::new();
21    /// if builder.validate().is_err() {
22    ///     // Handle validation error
23    /// }
24    /// ```
25    #[must_use = "validation result should be checked"]
26    pub fn validate(self) -> Result<()> {
27        // Build and validate (extract_fields builds everything)
28        // We need to call extract_fields from the impl<'a> block
29        // Since validate doesn't need the lifetime, we can just build and drop
30        let (_version, nodes, messages, value_descriptions) = {
31            let version = self.version.build()?;
32            let nodes = self.nodes.build()?;
33            let messages: std::vec::Vec<Message> = self
34                .messages
35                .into_iter()
36                .map(|builder| builder.build())
37                .collect::<Result<std::vec::Vec<_>>>()?;
38            let mut value_descriptions_map: BTreeMap<
39                (Option<u32>, String),
40                crate::value_descriptions::ValueDescriptions,
41            > = BTreeMap::new();
42            for ((message_id, signal_name), vd_builder) in self.value_descriptions {
43                let vd: crate::value_descriptions::ValueDescriptions = vd_builder.build()?;
44                value_descriptions_map.insert((message_id, signal_name), vd);
45            }
46            let value_descriptions =
47                crate::dbc::ValueDescriptionsMap::from_map(value_descriptions_map);
48            (version, nodes, messages, value_descriptions)
49        };
50
51        // Validate messages
52        Validate::validate(&nodes, &messages, Some(&value_descriptions))?;
53
54        Ok(())
55    }
56
57    fn extract_fields(
58        self,
59    ) -> Result<(Version, Nodes, Messages, crate::dbc::ValueDescriptionsMap)> {
60        // Build version
61        let version = self.version.build()?;
62
63        // Build nodes (allow empty - DBC spec allows empty BU_: line)
64        let nodes = self.nodes.build()?;
65
66        // Build messages
67        // Collect into a temporary Vec first, then convert to slice for Messages::new
68        let messages_vec: std::vec::Vec<Message> = self
69            .messages
70            .into_iter()
71            .map(|builder| builder.build())
72            .collect::<Result<std::vec::Vec<_>>>()?;
73        let messages = Messages::new(&messages_vec)?;
74
75        // Build value descriptions
76        let mut value_descriptions_map: BTreeMap<
77            (Option<u32>, String),
78            crate::value_descriptions::ValueDescriptions,
79        > = BTreeMap::new();
80        for ((message_id, signal_name), vd_builder) in self.value_descriptions {
81            let vd: crate::value_descriptions::ValueDescriptions = vd_builder.build()?;
82            value_descriptions_map.insert((message_id, signal_name), vd);
83        }
84        let value_descriptions = crate::dbc::ValueDescriptionsMap::from_map(value_descriptions_map);
85
86        Ok((version, nodes, messages, value_descriptions))
87    }
88
89    /// Builds the `Dbc` from the builder.
90    ///
91    /// This method validates all fields and constructs the `Dbc` instance.
92    /// Returns an error if validation fails.
93    ///
94    /// # Examples
95    ///
96    /// ```rust,no_run
97    /// use dbc_rs::{DbcBuilder, VersionBuilder, NodesBuilder};
98    ///
99    /// let dbc = DbcBuilder::new()
100    ///     .version(VersionBuilder::new().version("1.0"))
101    ///     .nodes(NodesBuilder::new().add_node("ECM"))
102    ///     .build()?;
103    /// # Ok::<(), dbc_rs::Error>(())
104    /// ```
105    pub fn build(self) -> Result<Dbc> {
106        let (version, nodes, messages, value_descriptions) = self.extract_fields()?;
107        // Validate before construction
108        // Get slice from Messages for validation
109        let messages_slice: std::vec::Vec<Message> = messages.iter().cloned().collect();
110        Validate::validate(&nodes, &messages_slice, Some(&value_descriptions))?;
111        // TODO: Add extended multiplexing
112        let extended_multiplexing: crate::compat::Vec<
113            ExtendedMultiplexing,
114            { MAX_EXTENDED_MULTIPLEXING },
115        > = crate::compat::Vec::new();
116        Ok(Dbc::new(
117            Some(version),
118            nodes,
119            messages,
120            value_descriptions,
121            extended_multiplexing,
122        ))
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    #![allow(clippy::float_cmp)]
129    use super::DbcBuilder;
130    use crate::{
131        ByteOrder, MessageBuilder, NodesBuilder, ReceiversBuilder, SignalBuilder, VersionBuilder,
132    };
133
134    #[test]
135    fn test_dbc_builder_valid() {
136        let version = VersionBuilder::new().version("1.0");
137        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
138        let signal = SignalBuilder::new()
139            .name("RPM")
140            .start_bit(0)
141            .length(16)
142            .byte_order(ByteOrder::BigEndian)
143            .unsigned(true)
144            .factor(1.0)
145            .offset(0.0)
146            .min(0.0)
147            .max(100.0)
148            .receivers(ReceiversBuilder::new().none());
149        let message = MessageBuilder::new()
150            .id(256)
151            .name("EngineData")
152            .dlc(8)
153            .sender("ECM")
154            .add_signal(signal);
155
156        let dbc = DbcBuilder::new()
157            .version(version)
158            .nodes(nodes)
159            .add_message(message)
160            .build()
161            .unwrap();
162
163        assert_eq!(dbc.messages().len(), 1);
164        assert_eq!(dbc.messages().at(0).unwrap().id(), 256);
165    }
166
167    #[test]
168    fn test_dbc_builder_missing_version() {
169        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
170        let signal = SignalBuilder::new()
171            .name("RPM")
172            .start_bit(0)
173            .length(16)
174            .byte_order(ByteOrder::BigEndian)
175            .unsigned(true)
176            .factor(1.0)
177            .offset(0.0)
178            .min(0.0)
179            .max(100.0)
180            .receivers(ReceiversBuilder::new().none());
181        let message = MessageBuilder::new()
182            .id(256)
183            .name("EngineData")
184            .dlc(8)
185            .sender("ECM")
186            .add_signal(signal);
187
188        let result = DbcBuilder::new().nodes(nodes).add_message(message).build();
189        // VersionBuilder now allows empty version, so this should succeed
190        assert!(result.is_ok());
191        let dbc = result.unwrap();
192        assert_eq!(dbc.version().unwrap().as_str(), "");
193    }
194
195    #[test]
196    fn test_dbc_builder_missing_nodes() {
197        // Empty nodes are now allowed per DBC spec
198        // When nodes are empty, sender validation is skipped
199        let version = VersionBuilder::new().version("1.0");
200        let signal = SignalBuilder::new()
201            .name("RPM")
202            .start_bit(0)
203            .length(16)
204            .byte_order(ByteOrder::BigEndian)
205            .unsigned(true)
206            .factor(1.0)
207            .offset(0.0)
208            .min(0.0)
209            .max(100.0)
210            .receivers(ReceiversBuilder::new().none());
211        let message = MessageBuilder::new()
212            .id(256)
213            .name("EngineData")
214            .dlc(8)
215            .sender("ECM")
216            .add_signal(signal);
217
218        // Building without nodes should succeed (empty nodes allowed)
219        let result = DbcBuilder::new().version(version).add_message(message).build();
220        assert!(result.is_ok());
221        let dbc = result.unwrap();
222        assert!(dbc.nodes().is_empty());
223    }
224
225    #[test]
226    fn test_dbc_builder_validate_missing_version() {
227        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
228        // VersionBuilder now allows empty version, so validation should succeed
229        let result = DbcBuilder::new().nodes(nodes).validate();
230        assert!(result.is_ok());
231    }
232
233    #[test]
234    fn test_dbc_builder_validate_missing_nodes() {
235        // Empty nodes are now allowed per DBC spec
236        let version = VersionBuilder::new().version("1.0");
237        let result = DbcBuilder::new().version(version).validate();
238        // Validation should succeed with empty nodes
239        assert!(result.is_ok());
240    }
241
242    #[test]
243    fn test_dbc_builder_validate_valid() {
244        let version = VersionBuilder::new().version("1.0");
245        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
246        let signal = SignalBuilder::new()
247            .name("RPM")
248            .start_bit(0)
249            .length(16)
250            .byte_order(ByteOrder::BigEndian)
251            .unsigned(true)
252            .factor(1.0)
253            .offset(0.0)
254            .min(0.0)
255            .max(100.0)
256            .receivers(ReceiversBuilder::new().none());
257        let message = MessageBuilder::new()
258            .id(256)
259            .name("EngineData")
260            .dlc(8)
261            .sender("ECM")
262            .add_signal(signal);
263
264        // validate() consumes the builder, so we can't use it after
265        // But we can check it doesn't error
266        let builder = DbcBuilder::new().version(version).nodes(nodes).add_message(message);
267        let result = builder.validate();
268        assert!(result.is_ok());
269    }
270}