dbc_rs/dbc/builder/
core.rs

1use super::DbcBuilder;
2use crate::{
3    Dbc, MessageBuilder, NodesBuilder, Receivers, ReceiversBuilder, SignalBuilder,
4    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    /// Creates a `DbcBuilder` from an existing `Dbc`.
32    ///
33    /// This allows you to modify an existing DBC file by creating a builder
34    /// initialized with all data from the provided DBC.
35    ///
36    /// # Arguments
37    ///
38    /// * `dbc` - The existing `Dbc` to create a builder from
39    ///
40    /// # Examples
41    ///
42    /// ```rust,no_run
43    /// use dbc_rs::{Dbc, DbcBuilder, MessageBuilder};
44    ///
45    /// let original = Dbc::parse(r#"VERSION "1.0"\nBU_: ECM\n"#)?;
46    /// let modified = DbcBuilder::from_dbc(&original)
47    ///     .add_message(MessageBuilder::new().id(256).name("Msg").dlc(8).sender("ECM"))
48    ///     .build()?;
49    /// # Ok::<(), dbc_rs::Error>(())
50    /// ```
51    pub fn from_dbc(dbc: &Dbc) -> Self {
52        // Convert version to builder (store builder, not final type)
53        let version = if let Some(v) = dbc.version() {
54            VersionBuilder::new().version(v.as_str())
55        } else {
56            VersionBuilder::new()
57        };
58
59        // Convert nodes to builder (store builder, not final type)
60        // Note: We unwrap here because we're converting from a valid Dbc, so names should already fit MAX_NAME_SIZE
61        let nodes = {
62            let mut builder = NodesBuilder::new();
63            for node in dbc.nodes().iter() {
64                // Convert compat::String to std::string::String for the builder
65                let node_str = node.to_string();
66                // Should never fail for valid Dbc - unwrap is safe
67                builder = builder.add_node(node_str);
68            }
69            builder
70        };
71
72        // Convert messages to builders (store builders, not final types)
73        let messages: Vec<MessageBuilder> = dbc
74            .messages()
75            .iter()
76            .map(|msg| {
77                let mut msg_builder = MessageBuilder::new()
78                    .id(msg.id())
79                    .name(msg.name())
80                    .dlc(msg.dlc())
81                    .sender(msg.sender());
82
83                // Convert signals using SignalBuilder
84                for sig in msg.signals().iter() {
85                    let mut sig_builder = SignalBuilder::new()
86                        .name(sig.name())
87                        .start_bit(sig.start_bit())
88                        .length(sig.length())
89                        .byte_order(sig.byte_order())
90                        .unsigned(sig.is_unsigned())
91                        .factor(sig.factor())
92                        .offset(sig.offset())
93                        .min(sig.min())
94                        .max(sig.max());
95
96                    if let Some(unit) = sig.unit() {
97                        sig_builder = sig_builder.unit(unit);
98                    }
99
100                    // Convert receivers using ReceiversBuilder
101                    let receivers_builder = match sig.receivers() {
102                        Receivers::Broadcast => ReceiversBuilder::new().broadcast(),
103                        Receivers::None => ReceiversBuilder::new().none(),
104                        Receivers::Nodes(nodes) => {
105                            let mut rb = ReceiversBuilder::new();
106                            // nodes is Vec<String<{MAX_NAME_SIZE}>>, iterate directly
107                            for receiver in nodes.iter() {
108                                // receiver is &String<{MAX_NAME_SIZE}>, clone it
109                                let receiver_str = receiver.clone();
110                                // Should never fail for valid Dbc - unwrap is safe
111                                rb = rb.add_node(receiver_str);
112                            }
113                            rb
114                        }
115                    };
116                    sig_builder = sig_builder.receivers(receivers_builder);
117
118                    msg_builder = msg_builder.add_signal(sig_builder);
119                }
120
121                msg_builder
122            })
123            .collect();
124
125        // Convert value descriptions from Dbc to builder format (store builders, not final types)
126        let mut value_descriptions: BTreeMap<(Option<u32>, String), ValueDescriptionsBuilder> =
127            BTreeMap::new();
128        for ((message_id, signal_name), vd) in dbc.value_descriptions().iter() {
129            // Store as String and ValueDescriptionsBuilder (no leak)
130            let mut builder = ValueDescriptionsBuilder::new();
131            for (value, desc) in vd.iter() {
132                builder = builder.add_entry(value, desc);
133            }
134            value_descriptions.insert((message_id, signal_name.to_string()), builder);
135        }
136
137        Self {
138            version,
139            nodes,
140            messages,
141            value_descriptions,
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::DbcBuilder;
149    use crate::{Dbc, MessageBuilder};
150
151    #[test]
152    fn test_dbc_builder_from_dbc() {
153        // Parse an existing DBC
154        let dbc_content = r#"VERSION "1.0"
155
156BU_: ECM TCM
157
158BO_ 256 Engine : 8 ECM
159 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
160"#;
161        let original_dbc = Dbc::parse(dbc_content).unwrap();
162
163        // Create builder from existing DBC
164        let modified_dbc = DbcBuilder::from_dbc(&original_dbc)
165            .add_message(MessageBuilder::new().id(512).name("Brake").dlc(4).sender("TCM"))
166            .build()
167            .unwrap();
168
169        // Verify original data is preserved
170        assert_eq!(modified_dbc.version().map(|v| v.as_str()), Some("1.0"));
171        assert_eq!(modified_dbc.nodes().len(), 2);
172        assert!(modified_dbc.nodes().contains("ECM"));
173        assert!(modified_dbc.nodes().contains("TCM"));
174
175        // Verify original message is present
176        assert_eq!(modified_dbc.messages().len(), 2);
177        assert!(modified_dbc.messages().iter().any(|m| m.id() == 256));
178        assert!(modified_dbc.messages().iter().any(|m| m.id() == 512));
179
180        // Verify original message's signal is preserved
181        let engine_msg = modified_dbc.messages().iter().find(|m| m.id() == 256).unwrap();
182        assert_eq!(engine_msg.signals().len(), 1);
183        assert_eq!(engine_msg.signals().at(0).unwrap().name(), "RPM");
184    }
185
186    #[test]
187    fn test_dbc_builder_from_dbc_empty() {
188        // Parse a minimal DBC
189        let dbc_content = r#"VERSION "1.0"
190
191BU_:
192"#;
193        let original_dbc = Dbc::parse(dbc_content).unwrap();
194
195        // Create builder from existing DBC
196        let modified_dbc = DbcBuilder::from_dbc(&original_dbc)
197            .add_message(MessageBuilder::new().id(256).name("Test").dlc(8).sender("ECM"))
198            .build()
199            .unwrap();
200
201        // Verify version is preserved
202        assert_eq!(modified_dbc.version().map(|v| v.as_str()), Some("1.0"));
203        // Empty nodes are preserved
204        assert!(modified_dbc.nodes().is_empty());
205        // New message is added
206        assert_eq!(modified_dbc.messages().len(), 1);
207    }
208}