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}