dbc_rs/version/
version.rs

1use crate::{
2    Parser,
3    error::{ParseError, ParseResult, lang},
4};
5
6/// Represents a version string from a DBC file.
7///
8/// The `VERSION` statement in a DBC file specifies the database version.
9/// This struct stores the version string as a borrowed reference.
10///
11/// # Examples
12///
13/// ```rust,no_run
14/// use dbc_rs::Dbc;
15///
16/// let dbc_content = r#"VERSION "1.0"
17///
18/// BU_: ECM
19///
20/// BO_ 256 Engine : 8 ECM
21///  SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
22/// "#;
23///
24/// let dbc = Dbc::parse(dbc_content)?;
25/// if let Some(version) = dbc.version() {
26///     println!("DBC version: {}", version);
27///     // Access the raw string
28///     assert_eq!(version.as_str(), "1.0");
29/// }
30/// # Ok::<(), dbc_rs::Error>(())
31/// ```
32///
33/// # Format
34///
35/// The version string can be any sequence of printable characters enclosed in quotes.
36/// Common formats include:
37/// - `"1.0"` - Simple semantic version
38/// - `"1.2.3"` - Full semantic version
39/// - `"1.0-beta"` - Version with suffix
40/// - `""` - Empty version string (allowed)
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct Version<'a> {
43    version: &'a str,
44}
45
46impl<'a> Version<'a> {
47    /// Creates a new `Version` from a version string.
48    ///
49    /// # Note
50    ///
51    /// This method is intended for internal use. For parsing from DBC content,
52    /// use `Version::parse()`. For programmatic construction, use `VersionBuilder`
53    /// (requires `alloc` feature).
54    ///
55    /// # Arguments
56    ///
57    /// * `version` - The version string (should be validated before calling this)
58    pub(crate) fn new(version: &'a str) -> Self {
59        // Validation should have been done prior (by builder or parse)
60        Self { version }
61    }
62
63    /// Parses a `VERSION` statement from a DBC file.
64    ///
65    /// This method expects the parser to be positioned at or after the `VERSION` keyword.
66    /// It will parse the version string enclosed in quotes.
67    ///
68    /// # Format
69    ///
70    /// The expected format is: `VERSION "version_string"`
71    ///
72    /// # Arguments
73    ///
74    /// * `parser` - The parser positioned at the VERSION statement
75    ///
76    /// # Returns
77    ///
78    /// Returns `Ok(Version)` if parsing succeeds, or `Err(ParseError)` if:
79    /// - The opening quote is missing
80    /// - The closing quote is missing
81    /// - The version string exceeds the maximum length (255 characters)
82    /// - The version string contains invalid UTF-8
83    ///
84    /// # Examples
85    ///
86    /// ```rust,no_run
87    /// use dbc_rs::Dbc;
88    ///
89    /// // Version is typically accessed from a parsed DBC
90    /// let dbc = Dbc::parse(r#"VERSION "1.0"
91    ///
92    /// BU_: ECM
93    /// "#)?;
94    ///
95    /// if let Some(version) = dbc.version() {
96    ///     assert_eq!(version.as_str(), "1.0");
97    /// }
98    /// # Ok::<(), dbc_rs::Error>(())
99    /// ```
100    #[must_use = "parse result should be checked"]
101    pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
102        use crate::VERSION;
103        // Version parsing must always start with "VERSION" keyword
104        parser
105            .expect(VERSION.as_bytes())
106            .map_err(|_| ParseError::Expected("Expected 'VERSION' keyword"))?;
107
108        // Skip whitespace and expect quote
109        parser
110            .skip_whitespace()?
111            .expect(b"\"")
112            .map_err(|_| ParseError::Expected("Expected opening quote after VERSION"))?;
113
114        // Read version content until closing quote (allow any printable characters)
115        // Use a reasonable max length for version strings (e.g., 255 characters)
116        // Note: take_until_quote already advances past the closing quote
117        const MAX_VERSION_LENGTH: u16 = 255;
118        let version_bytes = parser.take_until_quote(false, MAX_VERSION_LENGTH)?;
119
120        // Convert bytes to string slice using the parser's input
121        let version_str = core::str::from_utf8(version_bytes)
122            .map_err(|_| ParseError::Version(lang::VERSION_INVALID))?;
123
124        // Construct directly (validation already done during parsing)
125        Ok(Version::new(version_str))
126    }
127
128    /// Returns the version string as a `&str`.
129    ///
130    /// # Examples
131    ///
132    /// ```rust,no_run
133    /// use dbc_rs::Dbc;
134    ///
135    /// let dbc = Dbc::parse(r#"VERSION "1.2.3"
136    ///
137    /// BU_: ECM
138    /// "#)?;
139    ///
140    /// if let Some(version) = dbc.version() {
141    ///     assert_eq!(version.as_str(), "1.2.3");
142    /// }
143    /// # Ok::<(), dbc_rs::Error>(())
144    /// ```
145    #[must_use]
146    pub fn as_str(&self) -> &'a str {
147        self.version
148    }
149
150    /// Converts the version to its DBC file representation.
151    ///
152    /// Returns a string in the format: `VERSION "version_string"`
153    ///
154    /// # Examples
155    ///
156    /// ```rust,no_run
157    /// use dbc_rs::Dbc;
158    ///
159    /// let dbc = Dbc::parse(r#"VERSION "1.0"
160    ///
161    /// BU_: ECM
162    /// "#)?;
163    ///
164    /// if let Some(version) = dbc.version() {
165    ///     let dbc_string = version.to_dbc_string();
166    ///     assert_eq!(dbc_string, "VERSION \"1.0\"");
167    /// }
168    /// # Ok::<(), dbc_rs::Error>(())
169    /// ```
170    ///
171    /// # Empty Version
172    ///
173    /// Empty version strings are represented as `VERSION ""`:
174    ///
175    /// ```rust,no_run
176    /// use dbc_rs::Dbc;
177    ///
178    /// let dbc = Dbc::parse(r#"VERSION ""
179    ///
180    /// BU_: ECM
181    /// "#)?;
182    ///
183    /// if let Some(version) = dbc.version() {
184    ///     assert_eq!(version.to_dbc_string(), "VERSION \"\"");
185    /// }
186    /// # Ok::<(), dbc_rs::Error>(())
187    /// ```
188    ///
189    /// # Feature Requirements
190    ///
191    /// This method requires the `alloc` feature to be enabled.
192    #[must_use]
193    #[cfg(feature = "alloc")]
194    pub fn to_dbc_string(&self) -> alloc::string::String {
195        use crate::VERSION;
196        use alloc::format;
197        if self.version.is_empty() {
198            format!("{} \"\"", VERSION)
199        } else {
200            format!("{} \"{}\"", VERSION, self.version)
201        }
202    }
203}
204
205/// Display implementation for `Version`.
206///
207/// Formats the version as just the version string (without the `VERSION` keyword or quotes).
208///
209/// # Examples
210///
211/// ```rust,no_run
212/// use dbc_rs::Dbc;
213///
214/// let dbc = Dbc::parse(r#"VERSION "1.2.3"
215///
216/// BU_: ECM
217/// "#)?;
218///
219/// if let Some(version) = dbc.version() {
220///     // Display trait formats as just the version string
221///     assert_eq!(format!("{}", version), "1.2.3");
222///     // Use to_dbc_string() for full DBC format (requires alloc feature)
223///     #[cfg(feature = "alloc")]
224///     assert_eq!(version.to_dbc_string(), "VERSION \"1.2.3\"");
225/// }
226/// # Ok::<(), dbc_rs::Error>(())
227/// ```
228impl<'a> core::fmt::Display for Version<'a> {
229    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
230        write!(f, "{}", self.version)
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::Version;
237    use crate::Parser;
238    use crate::error::ParseError;
239
240    // Helper function to assert version string (works in all configurations)
241    fn assert_version_str(version: &Version, expected: &str) {
242        assert_eq!(version.as_str(), expected);
243        #[cfg(any(feature = "alloc", feature = "kernel"))]
244        {
245            #[cfg(feature = "alloc")]
246            use alloc::string::ToString;
247            #[cfg(all(feature = "kernel", not(feature = "alloc")))]
248            {
249                // In kernel mode, Display works but to_string() requires alloc
250                use alloc::format;
251                let formatted = format!("{}", version);
252                assert_eq!(formatted, expected);
253            }
254            #[cfg(feature = "alloc")]
255            assert_eq!(version.to_string(), expected);
256        }
257    }
258
259    // Tests that work in all configurations
260    #[test]
261    fn test_read_version() {
262        let line = b"VERSION \"1.0\"";
263        let mut parser = Parser::new(line).unwrap();
264        let version = Version::parse(&mut parser).unwrap();
265        assert_version_str(&version, "1.0");
266    }
267
268    #[test]
269    fn test_read_version_invalid() {
270        let line = b"VERSION 1.0";
271        let mut parser = Parser::new(line).unwrap();
272        let version = Version::parse(&mut parser).unwrap_err();
273        match version {
274            ParseError::Expected(_) => {}
275            _ => panic!("Expected Expected error, got {:?}", version),
276        }
277    }
278
279    #[test]
280    fn test_version_parse_empty() {
281        let line = b"";
282        let result = Parser::new(line);
283        assert!(result.is_err());
284        match result.unwrap_err() {
285            ParseError::UnexpectedEof => {}
286            _ => panic!("Expected UnexpectedEof"),
287        }
288    }
289
290    #[test]
291    fn test_version_parse_no_version_prefix() {
292        let line = b"\"1.0\"";
293        let mut parser = Parser::new(line).unwrap();
294        let result = Version::parse(&mut parser);
295        assert!(result.is_err());
296        match result.unwrap_err() {
297            ParseError::Expected(_) => {}
298            _ => panic!("Expected Expected error"),
299        }
300    }
301
302    #[test]
303    fn test_version_parse_no_quotes() {
304        let line = b"VERSION 1.0";
305        let mut parser = Parser::new(line).unwrap();
306        let result = Version::parse(&mut parser);
307        assert!(result.is_err());
308        match result.unwrap_err() {
309            ParseError::Expected(_) => {}
310            _ => panic!("Expected Expected error"),
311        }
312    }
313
314    #[test]
315    fn test_version_parse_major_only() {
316        let line = b"VERSION \"1\"";
317        let mut parser = Parser::new(line).unwrap();
318        let result = Version::parse(&mut parser);
319        assert!(result.is_ok());
320        let version = result.unwrap();
321        assert_version_str(&version, "1");
322    }
323
324    #[test]
325    fn test_version_parse_full_version() {
326        let line = b"VERSION \"1.2.3\"";
327        let mut parser = Parser::new(line).unwrap();
328        let result = Version::parse(&mut parser);
329        assert!(result.is_ok());
330        let version = result.unwrap();
331        assert_version_str(&version, "1.2.3");
332    }
333
334    #[test]
335    fn test_version_parse_with_whitespace() {
336        let line = b"VERSION  \"1.0\"";
337        let mut parser = Parser::new(line).unwrap();
338        let result = Version::parse(&mut parser);
339        assert!(result.is_ok());
340        let version = result.unwrap();
341        assert_version_str(&version, "1.0");
342    }
343
344    #[test]
345    fn test_version_parse_empty_quotes() {
346        let line = b"VERSION \"\"";
347        let mut parser = Parser::new(line).unwrap();
348        let version = Version::parse(&mut parser).unwrap();
349        assert_version_str(&version, "");
350    }
351
352    #[test]
353    fn test_version_parse_missing_closing_quote() {
354        let line = b"VERSION \"1.0";
355        let mut parser = Parser::new(line).unwrap();
356        let result = Version::parse(&mut parser);
357        assert!(result.is_err());
358        match result.unwrap_err() {
359            ParseError::UnexpectedEof => {}
360            _ => panic!("Expected UnexpectedEof"),
361        }
362    }
363
364    #[test]
365    fn test_version_parse_missing_opening_quote() {
366        let line = b"VERSION 1.0\"";
367        let mut parser = Parser::new(line).unwrap();
368        let result = Version::parse(&mut parser);
369        assert!(result.is_err());
370        match result.unwrap_err() {
371            ParseError::Expected(_) => {}
372            _ => panic!("Expected Expected error"),
373        }
374    }
375
376    #[test]
377    fn test_version_with_special_chars() {
378        let line = b"VERSION \"1.0-beta\"";
379        let mut parser = Parser::new(line).unwrap();
380        let version = Version::parse(&mut parser).unwrap();
381        assert_version_str(&version, "1.0-beta");
382    }
383
384    // Tests that require alloc (to_dbc_string is only available with alloc)
385    #[cfg(feature = "alloc")]
386    mod tests_with_alloc {
387        use super::*;
388
389        #[test]
390        fn test_version_to_dbc_string() {
391            let line1 = b"VERSION \"1\"";
392            let mut parser1 = Parser::new(line1).unwrap();
393            let v1 = Version::parse(&mut parser1).unwrap();
394            assert_eq!(v1.to_dbc_string(), "VERSION \"1\"");
395
396            let line2 = b"VERSION \"1.0\"";
397            let mut parser2 = Parser::new(line2).unwrap();
398            let v2 = Version::parse(&mut parser2).unwrap();
399            assert_eq!(v2.to_dbc_string(), "VERSION \"1.0\"");
400
401            let line3 = b"VERSION \"2.3.4\"";
402            let mut parser3 = Parser::new(line3).unwrap();
403            let v3 = Version::parse(&mut parser3).unwrap();
404            assert_eq!(v3.to_dbc_string(), "VERSION \"2.3.4\"");
405        }
406
407        #[test]
408        fn test_version_empty_round_trip() {
409            let line = b"VERSION \"\"";
410            let mut parser = Parser::new(line).unwrap();
411            let version = Version::parse(&mut parser).unwrap();
412            assert_eq!(version.to_dbc_string(), "VERSION \"\"");
413        }
414    }
415}