dbc_rs/dbc/builder/
build.rs

1use super::DbcBuilder;
2use crate::{
3    Dbc, 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        Ok(Dbc::new(Some(version), nodes, messages, value_descriptions))
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    #![allow(clippy::float_cmp)]
118    use super::DbcBuilder;
119    use crate::{
120        ByteOrder, MessageBuilder, NodesBuilder, ReceiversBuilder, SignalBuilder, VersionBuilder,
121    };
122
123    #[test]
124    fn test_dbc_builder_valid() {
125        let version = VersionBuilder::new().version("1.0");
126        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
127        let signal = SignalBuilder::new()
128            .name("RPM")
129            .start_bit(0)
130            .length(16)
131            .byte_order(ByteOrder::BigEndian)
132            .unsigned(true)
133            .factor(1.0)
134            .offset(0.0)
135            .min(0.0)
136            .max(100.0)
137            .receivers(ReceiversBuilder::new().none());
138        let message = MessageBuilder::new()
139            .id(256)
140            .name("EngineData")
141            .dlc(8)
142            .sender("ECM")
143            .add_signal(signal);
144
145        let dbc = DbcBuilder::new()
146            .version(version)
147            .nodes(nodes)
148            .add_message(message)
149            .build()
150            .unwrap();
151
152        assert_eq!(dbc.messages().len(), 1);
153        assert_eq!(dbc.messages().at(0).unwrap().id(), 256);
154    }
155
156    #[test]
157    fn test_dbc_builder_missing_version() {
158        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
159        let signal = SignalBuilder::new()
160            .name("RPM")
161            .start_bit(0)
162            .length(16)
163            .byte_order(ByteOrder::BigEndian)
164            .unsigned(true)
165            .factor(1.0)
166            .offset(0.0)
167            .min(0.0)
168            .max(100.0)
169            .receivers(ReceiversBuilder::new().none());
170        let message = MessageBuilder::new()
171            .id(256)
172            .name("EngineData")
173            .dlc(8)
174            .sender("ECM")
175            .add_signal(signal);
176
177        let result = DbcBuilder::new().nodes(nodes).add_message(message).build();
178        // VersionBuilder now allows empty version, so this should succeed
179        assert!(result.is_ok());
180        let dbc = result.unwrap();
181        assert_eq!(dbc.version().unwrap().as_str(), "");
182    }
183
184    #[test]
185    fn test_dbc_builder_missing_nodes() {
186        // Empty nodes are now allowed per DBC spec
187        // When nodes are empty, sender validation is skipped
188        let version = VersionBuilder::new().version("1.0");
189        let signal = SignalBuilder::new()
190            .name("RPM")
191            .start_bit(0)
192            .length(16)
193            .byte_order(ByteOrder::BigEndian)
194            .unsigned(true)
195            .factor(1.0)
196            .offset(0.0)
197            .min(0.0)
198            .max(100.0)
199            .receivers(ReceiversBuilder::new().none());
200        let message = MessageBuilder::new()
201            .id(256)
202            .name("EngineData")
203            .dlc(8)
204            .sender("ECM")
205            .add_signal(signal);
206
207        // Building without nodes should succeed (empty nodes allowed)
208        let result = DbcBuilder::new().version(version).add_message(message).build();
209        assert!(result.is_ok());
210        let dbc = result.unwrap();
211        assert!(dbc.nodes().is_empty());
212    }
213
214    #[test]
215    fn test_dbc_builder_validate_missing_version() {
216        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
217        // VersionBuilder now allows empty version, so validation should succeed
218        let result = DbcBuilder::new().nodes(nodes).validate();
219        assert!(result.is_ok());
220    }
221
222    #[test]
223    fn test_dbc_builder_validate_missing_nodes() {
224        // Empty nodes are now allowed per DBC spec
225        let version = VersionBuilder::new().version("1.0");
226        let result = DbcBuilder::new().version(version).validate();
227        // Validation should succeed with empty nodes
228        assert!(result.is_ok());
229    }
230
231    #[test]
232    fn test_dbc_builder_validate_valid() {
233        let version = VersionBuilder::new().version("1.0");
234        let nodes = NodesBuilder::new().add_nodes(["ECM"]);
235        let signal = SignalBuilder::new()
236            .name("RPM")
237            .start_bit(0)
238            .length(16)
239            .byte_order(ByteOrder::BigEndian)
240            .unsigned(true)
241            .factor(1.0)
242            .offset(0.0)
243            .min(0.0)
244            .max(100.0)
245            .receivers(ReceiversBuilder::new().none());
246        let message = MessageBuilder::new()
247            .id(256)
248            .name("EngineData")
249            .dlc(8)
250            .sender("ECM")
251            .add_signal(signal);
252
253        // validate() consumes the builder, so we can't use it after
254        // But we can check it doesn't error
255        let builder = DbcBuilder::new().version(version).nodes(nodes).add_message(message);
256        let result = builder.validate();
257        assert!(result.is_ok());
258    }
259}