squawk_linter/
version.rs

1use std::num::ParseIntError;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, de::Error};
5
6#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
7pub struct Version {
8    major: i32,
9    minor: Option<i32>,
10    patch: Option<i32>,
11}
12
13impl Version {
14    #[must_use]
15    pub(crate) fn new(
16        major: i32,
17        minor: impl Into<Option<i32>>,
18        patch: impl Into<Option<i32>>,
19    ) -> Self {
20        Self {
21            major,
22            minor: minor.into(),
23            patch: patch.into(),
24        }
25    }
26}
27
28impl Default for Version {
29    fn default() -> Version {
30        Version::new(15, 0, 0)
31    }
32}
33
34// Allow us to deserialize our version from a string in .squawk.toml.
35// from https://stackoverflow.com/a/46755370/
36impl<'de> Deserialize<'de> for Version {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        let s: &str = Deserialize::deserialize(deserializer)?;
42        Version::from_str(s).map_err(D::Error::custom)
43    }
44}
45
46#[derive(Debug, PartialEq)]
47pub struct InvalidNumber {
48    pub version: String,
49    pub e: ParseIntError,
50}
51
52#[derive(Debug, PartialEq)]
53pub struct EmptyVersion {
54    pub version: String,
55}
56
57#[derive(Debug, PartialEq)]
58pub enum ParseVersionError {
59    EmptyVersion(EmptyVersion),
60    InvalidNumber(InvalidNumber),
61}
62
63fn parse_int(s: &str) -> Result<i32, ParseVersionError> {
64    Ok(s.parse().map_err(|e| {
65        ParseVersionError::InvalidNumber(InvalidNumber {
66            version: s.to_string(),
67            e,
68        })
69    }))?
70}
71
72impl std::fmt::Display for ParseVersionError {
73    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
74        match *self {
75            Self::EmptyVersion(ref err) => {
76                write!(f, "Empty version number provided: {:?}", err.version)
77            }
78            Self::InvalidNumber(ref err) => {
79                write!(
80                    f,
81                    "Invalid number in version: {:?}. Parse error: {}",
82                    err.version, err.e
83                )
84            }
85        }
86    }
87}
88
89impl std::error::Error for ParseVersionError {}
90
91impl FromStr for Version {
92    type Err = ParseVersionError;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        let version_pieces: Vec<&str> = s.split('.').collect();
96
97        if version_pieces.is_empty() {
98            return Err(ParseVersionError::EmptyVersion(EmptyVersion {
99                version: s.to_string(),
100            }));
101        }
102        let major = parse_int(version_pieces[0])?;
103
104        let minor: Option<i32> = if version_pieces.len() > 1 {
105            Some(parse_int(version_pieces[1])?)
106        } else {
107            None
108        };
109        let patch: Option<i32> = if version_pieces.len() > 2 {
110            Some(parse_int(version_pieces[2])?)
111        } else {
112            None
113        };
114
115        Ok(Version {
116            major,
117            minor,
118            patch,
119        })
120    }
121}
122
123#[cfg(test)]
124mod test_pg_version {
125    #![allow(clippy::neg_cmp_op_on_partial_ord)]
126    use insta::assert_debug_snapshot;
127
128    use super::*;
129    #[test]
130    fn eq() {
131        assert_eq!(Version::new(10, None, None), Version::new(10, None, None));
132    }
133    #[test]
134    fn gt() {
135        assert!(Version::new(10, Some(1), None) > Version::new(10, None, None));
136        assert!(Version::new(10, None, Some(1)) > Version::new(10, None, None));
137        assert!(Version::new(10, None, Some(1)) > Version::new(9, None, None));
138
139        assert!(!(Version::new(10, None, None) > Version::new(10, None, None)));
140    }
141    #[test]
142    fn parse() {
143        assert_eq!(
144            Version::from_str("10.1"),
145            Ok(Version::new(10, Some(1), None))
146        );
147        assert_eq!(Version::from_str("10"), Ok(Version::new(10, None, None)));
148        assert_eq!(
149            Version::from_str("10.2.1"),
150            Ok(Version::new(10, Some(2), Some(1)))
151        );
152        assert_debug_snapshot!(Version::from_str("test").unwrap_err());
153    }
154}