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}