dbc_rs/dbc/
serialize.rs

1use super::Dbc;
2use std::fmt::{Display, Formatter, Result};
3
4impl Dbc {
5    /// Serialize this DBC to a DBC format string
6    ///
7    /// # Examples
8    ///
9    /// ```rust,no_run
10    /// use dbc_rs::Dbc;
11    ///
12    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
13    /// let dbc_string = dbc.to_dbc_string();
14    /// // The string can be written to a file or used elsewhere
15    /// assert!(dbc_string.contains("VERSION"));
16    /// # Ok::<(), dbc_rs::Error>(())
17    /// ```
18    #[must_use = "return value should be used"]
19    pub fn to_dbc_string(&self) -> String {
20        // Pre-allocate with estimated capacity
21        // Estimate: ~50 chars per message + ~100 chars per signal
22        let signal_count: usize = self.messages().iter().map(|m| m.signals().len()).sum();
23        let estimated_capacity = 200 + (self.messages.len() * 50) + (signal_count * 100);
24        let mut result = String::with_capacity(estimated_capacity);
25
26        // VERSION line
27        if let Some(version) = &self.version {
28            result.push_str(&version.to_dbc_string());
29            result.push_str("\n\n");
30        }
31
32        // BU_ line
33        result.push_str(&self.nodes.to_dbc_string());
34        result.push('\n');
35
36        // BO_ and SG_ lines for each message
37        for message in self.messages().iter() {
38            result.push('\n');
39            result.push_str(&message.to_string_full());
40        }
41
42        result
43    }
44}
45
46impl Display for Dbc {
47    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
48        write!(f, "{}", self.to_dbc_string())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use crate::Dbc;
55
56    #[test]
57    fn test_to_dbc_string() {
58        let dbc = Dbc::parse(
59            r#"VERSION "1.0"
60
61BU_: ECM
62
63BO_ 256 Engine : 8 ECM
64 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
65"#,
66        )
67        .unwrap();
68
69        let dbc_string = dbc.to_dbc_string();
70        assert!(dbc_string.contains("VERSION"));
71        assert!(dbc_string.contains("BU_"));
72        assert!(dbc_string.contains("BO_"));
73        assert!(dbc_string.contains("SG_"));
74    }
75
76    #[test]
77    fn test_display() {
78        let dbc = Dbc::parse(
79            r#"VERSION "1.0"
80
81BU_: ECM
82
83BO_ 256 Engine : 8 ECM
84"#,
85        )
86        .unwrap();
87
88        let display_str = format!("{}", dbc);
89        assert!(display_str.contains("VERSION"));
90    }
91
92    #[test]
93    fn test_save_round_trip() {
94        let original = r#"VERSION "1.0"
95
96BU_: ECM TCM
97
98BO_ 256 EngineData : 8 ECM
99 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
100 SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM
101
102BO_ 512 BrakeData : 4 TCM
103 SG_ Pressure : 0|16@0+ (0.1,0) [0|1000] "bar"
104"#;
105
106        let dbc = Dbc::parse(original).unwrap();
107        let saved = dbc.to_dbc_string();
108        let dbc2 = Dbc::parse(&saved).unwrap();
109
110        // Verify round-trip: parsed data should match
111        assert_eq!(
112            dbc.version().map(|v| v.to_string()),
113            dbc2.version().map(|v| v.to_string())
114        );
115        assert_eq!(dbc.messages().len(), dbc2.messages().len());
116
117        for (msg1, msg2) in dbc.messages().iter().zip(dbc2.messages().iter()) {
118            assert_eq!(msg1.id(), msg2.id());
119            assert_eq!(msg1.name(), msg2.name());
120            assert_eq!(msg1.dlc(), msg2.dlc());
121            assert_eq!(msg1.sender(), msg2.sender());
122            assert_eq!(msg1.signals().len(), msg2.signals().len());
123
124            for (sig1, sig2) in msg1.signals().iter().zip(msg2.signals().iter()) {
125                assert_eq!(sig1.name(), sig2.name());
126                assert_eq!(sig1.start_bit(), sig2.start_bit());
127                assert_eq!(sig1.length(), sig2.length());
128                assert_eq!(sig1.byte_order(), sig2.byte_order());
129                assert_eq!(sig1.is_unsigned(), sig2.is_unsigned());
130                assert_eq!(sig1.factor(), sig2.factor());
131                assert_eq!(sig1.offset(), sig2.offset());
132                assert_eq!(sig1.min(), sig2.min());
133                assert_eq!(sig1.max(), sig2.max());
134                assert_eq!(sig1.unit(), sig2.unit());
135                assert_eq!(sig1.receivers(), sig2.receivers());
136            }
137        }
138    }
139
140    #[test]
141    fn test_save_basic() {
142        // Use parsing instead of builders
143        let dbc_content = r#"VERSION "1.0"
144
145BU_: ECM
146
147BO_ 256 EngineData : 8 ECM
148 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *
149"#;
150        let dbc = Dbc::parse(dbc_content).unwrap();
151
152        let saved = dbc.to_dbc_string();
153        assert!(saved.contains("VERSION \"1.0\""));
154        assert!(saved.contains("BU_: ECM"));
155        assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
156        assert!(saved.contains("SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *")); // BigEndian = @0
157    }
158
159    #[test]
160    fn test_save_multiple_messages() {
161        // Use parsing instead of builders
162        let dbc_content = r#"VERSION "1.0"
163
164BU_: ECM TCM
165
166BO_ 256 EngineData : 8 ECM
167 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
168
169BO_ 512 BrakeData : 4 TCM
170 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar"
171"#;
172        let dbc = Dbc::parse(dbc_content).unwrap();
173        let saved = dbc.to_dbc_string();
174
175        // Verify both messages are present
176        assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
177        assert!(saved.contains("BO_ 512 BrakeData : 4 TCM"));
178        assert!(saved.contains("SG_ RPM"));
179        assert!(saved.contains("SG_ Pressure"));
180    }
181}