1use alloc::{
2 format,
3 string::{String, ToString},
4 vec::Vec,
5};
6
7use crate::{Error, error::messages};
8
9#[derive(Debug)]
27pub struct Version {
28 major: u8,
29 minor: Option<u8>,
30 patch: Option<u8>,
31}
32
33impl Version {
34 pub fn builder() -> VersionBuilder {
47 VersionBuilder::new()
48 }
49
50 pub(crate) fn new(major: u8, minor: Option<u8>, patch: Option<u8>) -> Result<Self, Error> {
52 if minor.is_none() && patch.is_some() {
53 return Err(Error::Version(
54 messages::VERSION_PATCH_REQUIRES_MINOR.to_string(),
55 ));
56 }
57 Ok(Self {
58 major,
59 minor,
60 patch,
61 })
62 }
63
64 pub(super) fn parse(version: &str) -> Result<Self, Error> {
65 let version = if let Some(v) = version.strip_prefix("VERSION") {
67 v
68 } else {
69 return Err(Error::Version(messages::VERSION_EMPTY.to_string()));
70 }
71 .trim();
72
73 if version.is_empty() {
74 return Err(Error::Version(messages::VERSION_EMPTY.to_string()));
75 }
76
77 if !version.starts_with('"') || !version.ends_with('"') {
79 return Err(Error::Version(messages::VERSION_INVALID.to_string()));
80 }
81
82 let parts: Vec<&str> = version[1..version.len() - 1].split('.').collect();
83
84 if parts.is_empty() || parts.len() > 3 {
86 return Err(Error::Version(messages::VERSION_INVALID.to_string()));
87 }
88
89 let major =
91 parts[0].parse().map_err(|e| Error::Version(messages::parse_number_failed(e)))?;
92 let minor: Option<u8> = if parts.len() > 1 {
93 Some(parts[1].parse().map_err(|e| Error::Version(messages::parse_number_failed(e)))?)
94 } else {
95 None
96 };
97 let patch: Option<u8> = if parts.len() > 2 {
98 Some(parts[2].parse().map_err(|e| Error::Version(messages::parse_number_failed(e)))?)
99 } else {
100 None
101 };
102
103 Ok(Version {
104 major,
105 minor,
106 patch,
107 })
108 }
109
110 #[inline]
112 pub fn major(&self) -> u8 {
113 self.major
114 }
115
116 #[inline]
118 pub fn minor(&self) -> Option<u8> {
119 self.minor
120 }
121
122 #[inline]
124 pub fn patch(&self) -> Option<u8> {
125 self.patch
126 }
127
128 #[allow(clippy::inherent_to_string)]
130 pub fn to_string(&self) -> String {
131 match (self.minor, self.patch) {
132 (Some(minor), Some(patch)) => format!("{}.{}.{}", self.major, minor, patch),
133 (Some(minor), None) => format!("{}.{}", self.major, minor),
134 (None, _) => format!("{}", self.major),
135 }
136 }
137
138 pub fn to_dbc_string(&self) -> String {
152 format!("VERSION \"{}\"", self.to_string())
153 }
154}
155
156#[derive(Debug)]
176pub struct VersionBuilder {
177 major: Option<u8>,
178 minor: Option<u8>,
179 patch: Option<u8>,
180}
181
182impl VersionBuilder {
183 fn new() -> Self {
184 Self {
185 major: None,
186 minor: None,
187 patch: None,
188 }
189 }
190
191 pub fn major(mut self, major: u8) -> Self {
193 self.major = Some(major);
194 self
195 }
196
197 pub fn minor(mut self, minor: u8) -> Self {
199 self.minor = Some(minor);
200 self
201 }
202
203 pub fn patch(mut self, patch: u8) -> Self {
205 self.patch = Some(patch);
206 self
207 }
208
209 pub fn validate(&self) -> Result<(), Error> {
220 let _major = self
221 .major
222 .ok_or_else(|| Error::Version(messages::VERSION_MAJOR_REQUIRED.to_string()))?;
223
224 if self.minor.is_none() && self.patch.is_some() {
225 return Err(Error::Version(
226 messages::VERSION_PATCH_REQUIRES_MINOR.to_string(),
227 ));
228 }
229
230 Ok(())
231 }
232
233 pub fn build(self) -> Result<Version, Error> {
241 let major = self
242 .major
243 .ok_or_else(|| Error::Version(messages::VERSION_MAJOR_REQUIRED.to_string()))?;
244
245 Version::new(major, self.minor, self.patch)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::Version;
252 use crate::error::lang;
253 use crate::{Error, error::messages};
254
255 #[test]
256 fn test_read_version() {
257 let line = "VERSION \"1.0\"";
258 let version = Version::parse(line).unwrap();
259 assert_eq!(version.major(), 1);
260 assert_eq!(version.minor(), Some(0));
261 assert_eq!(version.patch(), None);
262 }
263
264 #[test]
265 fn test_read_version_invalid() {
266 let line = "VERSION 1.0";
267 let version = Version::parse(line).unwrap_err();
268 assert_eq!(
269 version,
270 Error::Version(messages::VERSION_INVALID.to_string())
271 );
272 }
273
274 #[test]
275 fn test_version_new() {
276 let v1 = Version::new(1, None, None).unwrap();
277 assert_eq!(v1.major(), 1);
278 assert_eq!(v1.minor(), None);
279 assert_eq!(v1.patch(), None);
280 assert_eq!(v1.to_string(), "1");
281
282 let v2 = Version::new(2, Some(3), None).unwrap();
283 assert_eq!(v2.major(), 2);
284 assert_eq!(v2.minor(), Some(3));
285 assert_eq!(v2.patch(), None);
286 assert_eq!(v2.to_string(), "2.3");
287
288 let v3 = Version::new(4, Some(5), Some(6)).unwrap();
289 assert_eq!(v3.major(), 4);
290 assert_eq!(v3.minor(), Some(5));
291 assert_eq!(v3.patch(), Some(6));
292 assert_eq!(v3.to_string(), "4.5.6");
293 }
294
295 #[test]
296 fn test_version_new_invalid_patch_without_minor() {
297 let result = Version::new(1, None, Some(2));
298 assert!(result.is_err());
299 assert_eq!(
300 result.unwrap_err(),
301 Error::Version(messages::VERSION_PATCH_REQUIRES_MINOR.to_string())
302 );
303 }
304
305 #[test]
306 fn test_version_parse_empty() {
307 let result = Version::parse("");
308 assert!(result.is_err());
309 match result.unwrap_err() {
310 Error::Version(msg) => assert!(msg.contains(lang::VERSION_EMPTY)),
311 _ => panic!("Expected InvalidData error"),
312 }
313 }
314
315 #[test]
316 fn test_version_parse_no_version_prefix() {
317 let result = Version::parse("\"1.0\"");
318 assert!(result.is_err());
319 match result.unwrap_err() {
320 Error::Version(msg) => assert!(msg.contains(lang::VERSION_EMPTY)),
321 _ => panic!("Expected InvalidData error"),
322 }
323 }
324
325 #[test]
326 fn test_version_parse_no_quotes() {
327 let result = Version::parse("VERSION 1.0");
328 assert!(result.is_err());
329 match result.unwrap_err() {
330 Error::Version(msg) => assert!(msg.contains(lang::VERSION_INVALID)),
331 _ => panic!("Expected InvalidData error"),
332 }
333 }
334
335 #[test]
336 fn test_version_parse_too_many_parts() {
337 let result = Version::parse("VERSION \"1.2.3.4\"");
338 assert!(result.is_err());
339 match result.unwrap_err() {
340 Error::Version(msg) => assert!(msg.contains(lang::VERSION_INVALID)),
341 _ => panic!("Expected InvalidData error"),
342 }
343 }
344
345 #[test]
346 fn test_version_parse_invalid_number() {
347 let result = Version::parse("VERSION \"abc\"");
348 assert!(result.is_err());
349 match result.unwrap_err() {
351 Error::Version(msg) => {
352 let template_text = lang::FORMAT_PARSE_NUMBER_FAILED.split("{}").next().unwrap();
355 assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
356 }
357 _ => panic!("Expected Version error from ParseIntError"),
358 }
359 }
360
361 #[test]
362 fn test_version_to_string_all_variants() {
363 let v1 = Version::new(1, None, None).unwrap();
364 assert_eq!(v1.to_string(), "1");
365
366 let v2 = Version::new(2, Some(3), None).unwrap();
367 assert_eq!(v2.to_string(), "2.3");
368
369 let v3 = Version::new(4, Some(5), Some(6)).unwrap();
370 assert_eq!(v3.to_string(), "4.5.6");
371 }
372
373 #[test]
374 fn test_version_to_dbc_string() {
375 let v1 = Version::new(1, None, None).unwrap();
376 assert_eq!(v1.to_dbc_string(), "VERSION \"1\"");
377
378 let v2 = Version::new(1, Some(0), None).unwrap();
379 assert_eq!(v2.to_dbc_string(), "VERSION \"1.0\"");
380
381 let v3 = Version::new(2, Some(3), Some(4)).unwrap();
382 assert_eq!(v3.to_dbc_string(), "VERSION \"2.3.4\"");
383 }
384}