dbc_rs/version/
version.rs

1use crate::{
2    Parser,
3    error::{ParseError, ParseResult, lang},
4};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct Version<'a> {
8    version: &'a str,
9}
10
11impl<'a> Version<'a> {
12    pub(crate) fn new(version: &'a str) -> Self {
13        // Validation should have been done prior (by builder or parse)
14        Self { version }
15    }
16
17    #[must_use = "parse result should be checked"]
18    pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
19        use crate::VERSION;
20        // Note: When called from Dbc::parse, find_next_keyword already consumed "VERSION"
21        // So we try to expect "VERSION" first, and if that fails, we're already past it
22        if parser.expect(VERSION.as_bytes()).is_ok() {
23            // Successfully consumed "VERSION", now skip whitespace and expect quote
24            parser
25                .skip_whitespace()?
26                .expect(b"\"")
27                .map_err(|_| ParseError::Expected("Expected opening quote after VERSION"))?;
28        } else {
29            // Check if we're at the start of input and input doesn't start with "VERSION"
30            // If so, "VERSION" is required
31            // Note: expect() doesn't change position on failure, so we can check is_at_start() here
32            if parser.is_at_start() && !parser.starts_with(VERSION.as_bytes()) {
33                // Use the constant in the error message (VERSION is "VERSION")
34                return Err(ParseError::Expected("Expected 'VERSION' keyword"));
35            }
36            // Already past "VERSION" from find_next_keyword
37            // find_next_keyword advances to right after "VERSION", which should be at whitespace or quote
38            // Skip whitespace if present, then expect quote
39            let _ = parser.skip_whitespace().ok(); // Ignore error if no whitespace
40            parser
41                .expect(b"\"")
42                .map_err(|_| ParseError::Expected("Expected opening quote after VERSION"))?;
43        }
44
45        // Read version content until closing quote (allow any printable characters)
46        // Use a reasonable max length for version strings (e.g., 255 characters)
47        // Note: take_until_quote already advances past the closing quote
48        const MAX_VERSION_LENGTH: u16 = 255;
49        let version_bytes = parser.take_until_quote(false, MAX_VERSION_LENGTH)?;
50
51        // Convert bytes to string slice using the parser's input
52        let version_str = core::str::from_utf8(version_bytes)
53            .map_err(|_| ParseError::Version(lang::VERSION_INVALID))?;
54
55        // Construct directly (validation already done during parsing)
56        Ok(Version::new(version_str))
57    }
58
59    pub fn as_str(&self) -> &'a str {
60        self.version
61    }
62
63    #[must_use]
64    #[cfg(feature = "alloc")]
65    pub fn to_dbc_string(&self) -> String {
66        use crate::VERSION;
67        if self.version.is_empty() {
68            format!("{} \"\"", VERSION)
69        } else {
70            format!("{} \"{}\"", VERSION, self.version)
71        }
72    }
73}
74
75// Display implementation
76impl<'a> core::fmt::Display for Version<'a> {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        write!(f, "{}", self.version)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::Version;
85    use crate::Parser;
86    use crate::error::ParseError;
87
88    #[test]
89    fn test_read_version() {
90        let line = b"VERSION \"1.0\"";
91        let mut parser = Parser::new(line).unwrap();
92        let version = Version::parse(&mut parser).unwrap();
93        #[cfg(feature = "alloc")]
94        assert_eq!(version.to_string(), "1.0");
95        #[cfg(not(feature = "alloc"))]
96        assert_eq!(version.as_str(), "1.0");
97        #[cfg(not(feature = "alloc"))]
98        assert_eq!(version.as_str(), "1.0");
99    }
100
101    #[test]
102    fn test_read_version_invalid() {
103        let line = b"VERSION 1.0";
104        let mut parser = Parser::new(line).unwrap();
105        let version = Version::parse(&mut parser).unwrap_err();
106        // When there's no quote after VERSION, we get Expected error
107        match version {
108            ParseError::Expected(_) => {
109                // This is expected - we're looking for a quote but found space/1
110            }
111            _ => panic!("Expected Expected error, got {:?}", version),
112        }
113    }
114
115    #[test]
116    fn test_version_parse_empty() {
117        let line = b"";
118        let result = Parser::new(line);
119        assert!(result.is_err());
120        match result.unwrap_err() {
121            ParseError::UnexpectedEof => {
122                // Empty input results in UnexpectedEof
123            }
124            _ => panic!("Expected UnexpectedEof"),
125        }
126    }
127
128    #[test]
129    fn test_version_parse_no_version_prefix() {
130        let line = b"\"1.0\"";
131        let mut parser = Parser::new(line).unwrap();
132        let result = Version::parse(&mut parser);
133        assert!(result.is_err());
134        match result.unwrap_err() {
135            ParseError::Expected(_) => {
136                // Expected "VERSION" but got quote
137            }
138            _ => panic!("Expected Expected error"),
139        }
140    }
141
142    #[test]
143    fn test_version_parse_no_quotes() {
144        let line = b"VERSION 1.0";
145        let mut parser = Parser::new(line).unwrap();
146        let result = Version::parse(&mut parser);
147        assert!(result.is_err());
148        match result.unwrap_err() {
149            ParseError::Expected(_) => {
150                // Expected quote but got space/1
151            }
152            _ => panic!("Expected Expected error"),
153        }
154    }
155
156    #[test]
157    fn test_version_parse_major_only() {
158        let line = b"VERSION \"1\"";
159        let mut parser = Parser::new(line).unwrap();
160        let result = Version::parse(&mut parser);
161        assert!(result.is_ok());
162        let version = result.unwrap();
163        #[cfg(feature = "alloc")]
164        assert_eq!(version.to_string(), "1");
165        #[cfg(not(feature = "alloc"))]
166        assert_eq!(version.as_str(), "1");
167    }
168
169    #[test]
170    fn test_version_parse_full_version() {
171        let line = b"VERSION \"1.2.3\"";
172        let mut parser = Parser::new(line).unwrap();
173        let result = Version::parse(&mut parser);
174        assert!(result.is_ok());
175        let version = result.unwrap();
176        #[cfg(feature = "std")]
177        assert_eq!(version.to_string(), "1.2.3");
178        #[cfg(not(feature = "alloc"))]
179        assert_eq!(version.as_str(), "1.2.3");
180    }
181
182    #[test]
183    fn test_version_parse_with_whitespace() {
184        let line = b"VERSION  \"1.0\"";
185        let mut parser = Parser::new(line).unwrap();
186        let result = Version::parse(&mut parser);
187        assert!(result.is_ok());
188        let version = result.unwrap();
189        #[cfg(feature = "std")]
190        assert_eq!(version.to_string(), "1.0");
191        #[cfg(not(feature = "alloc"))]
192        assert_eq!(version.as_str(), "1.0");
193    }
194
195    #[test]
196    fn test_version_parse_empty_quotes() {
197        let line = b"VERSION \"\"";
198        let mut parser = Parser::new(line).unwrap();
199        let version = Version::parse(&mut parser).unwrap();
200        #[cfg(feature = "std")]
201        assert_eq!(version.to_string(), "");
202        #[cfg(not(feature = "alloc"))]
203        assert_eq!(version.as_str(), "");
204    }
205
206    #[test]
207    fn test_version_parse_missing_closing_quote() {
208        let line = b"VERSION \"1.0";
209        let mut parser = Parser::new(line).unwrap();
210        let result = Version::parse(&mut parser);
211        assert!(result.is_err());
212        match result.unwrap_err() {
213            ParseError::UnexpectedEof => {
214                // Reached EOF without finding closing quote
215            }
216            _ => panic!("Expected UnexpectedEof"),
217        }
218    }
219
220    #[test]
221    fn test_version_parse_missing_opening_quote() {
222        let line = b"VERSION 1.0\"";
223        let mut parser = Parser::new(line).unwrap();
224        let result = Version::parse(&mut parser);
225        assert!(result.is_err());
226        match result.unwrap_err() {
227            ParseError::Expected(_) => {
228                // Expected quote but got space/1
229            }
230            _ => panic!("Expected Expected error"),
231        }
232    }
233
234    #[test]
235    #[cfg(feature = "alloc")]
236    fn test_version_to_dbc_string() {
237        let line1 = b"VERSION \"1\"";
238        let mut parser1 = Parser::new(line1).unwrap();
239        let v1 = Version::parse(&mut parser1).unwrap();
240        assert_eq!(v1.to_dbc_string(), "VERSION \"1\"");
241
242        let line2 = b"VERSION \"1.0\"";
243        let mut parser2 = Parser::new(line2).unwrap();
244        let v2 = Version::parse(&mut parser2).unwrap();
245        assert_eq!(v2.to_dbc_string(), "VERSION \"1.0\"");
246
247        let line3 = b"VERSION \"2.3.4\"";
248        let mut parser3 = Parser::new(line3).unwrap();
249        let v3 = Version::parse(&mut parser3).unwrap();
250        assert_eq!(v3.to_dbc_string(), "VERSION \"2.3.4\"");
251    }
252
253    #[test]
254    #[cfg(feature = "alloc")]
255    fn test_version_empty_round_trip() {
256        let line = b"VERSION \"\"";
257        let mut parser = Parser::new(line).unwrap();
258        let version = Version::parse(&mut parser).unwrap();
259        assert_eq!(version.to_dbc_string(), "VERSION \"\"");
260    }
261
262    #[test]
263    fn test_version_with_special_chars() {
264        let line = b"VERSION \"1.0-beta\"";
265        let mut parser = Parser::new(line).unwrap();
266        let version = Version::parse(&mut parser).unwrap();
267        #[cfg(feature = "std")]
268        assert_eq!(version.to_string(), "1.0-beta");
269        #[cfg(not(feature = "alloc"))]
270        assert_eq!(version.as_str(), "1.0-beta");
271    }
272}