dbc_rs/version/
version.rs

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