version_rs/
lib.rs

1#[cfg(test)]
2mod tests {
3    use super::Version;
4    #[test]
5    fn from_str() {
6        assert_eq!(Version::new(0, 1, 0), "0.1".parse().ok().unwrap());
7        assert_eq!(Version::new(0, 1, 0), "0.1.0".parse().ok().unwrap());
8        assert!("0.1.".parse::<Version>().is_err());
9        assert!("0..".parse::<Version>().is_err());
10        assert!("..".parse::<Version>().is_err());
11        assert!("0.1.0.0".parse::<Version>().is_err());
12    }
13
14    #[test]
15    fn derive() {
16        let one: u32 = 1;
17        assert_eq!(Version::from((one, 2, 3)), (one, 2, 3).into());
18        assert!(Version::from(1) < Version::from(2));
19    }
20}
21
22#[cfg(not(feature = "serde"))]
23#[derive(Eq, PartialEq, Clone)]
24pub struct Version {
25    pub major: u32,
26    pub minor: u32,
27    pub revision: u32,
28}
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[cfg(feature = "serde")]
34#[derive(Eq, PartialEq, Clone, Deserialize, Serialize)]
35#[serde(into = "String")]
36#[serde(try_from = "&str")]
37pub struct Version {
38    pub major: u32,
39    pub minor: u32,
40    pub revision: u32,
41}
42
43impl std::fmt::Display for Version {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
45        write!(f, "{}.{}.{}", self.major, self.minor, self.revision)
46    }
47}
48
49impl std::fmt::Debug for Version {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
51        write!(f, "v{}.{}.{}", self.major, self.minor, self.revision)
52    }
53}
54
55impl std::cmp::PartialOrd for Version {
56    fn partial_cmp(&self, r: &Version) -> std::option::Option<std::cmp::Ordering> {
57        match self.major.cmp(&r.major) {
58            std::cmp::Ordering::Equal => match self.minor.cmp(&r.minor) {
59                std::cmp::Ordering::Equal => Some(self.revision.cmp(&r.revision)),
60                other @ _ => Some(other),
61            },
62            other @ _ => Some(other),
63        }
64    }
65}
66
67impl std::cmp::Ord for Version {
68    fn cmp(&self, r: &Self) -> std::cmp::Ordering {
69        self.partial_cmp(r).unwrap()
70    }
71}
72
73// conflicting dependency issues
74// impl<N: std::convert::Into<u32>> From<N> for Version {
75//     fn from(elem: N) -> Self {
76//         Version {
77//             major: elem.into(),
78//             minor: 0,
79//             revision: 0,
80//         }
81//     }
82// }
83
84macro_rules! primitive_from {
85    ($type:tt) => {
86        impl From<$type> for Version {
87            fn from(elem: $type) -> Self {
88                Version {
89                    major: elem as u32,
90                    minor: 0,
91                    revision: 0,
92                }
93            }
94        }
95    };
96
97    ($first:tt, $($rest:tt),+) => {
98        primitive_from! {$first}
99        primitive_from! {$($rest),+}
100    };
101}
102
103primitive_from! {u32, u8, u16, i16, i8, i32, usize}
104
105impl<N: std::convert::Into<u32>> From<(N, N)> for Version {
106    fn from(elem: (N, N)) -> Self {
107        Version {
108            major: elem.0.into(),
109            minor: elem.1.into(),
110            revision: 0,
111        }
112    }
113}
114
115impl<N: std::convert::Into<u32>> From<(N, N, N)> for Version {
116    fn from(elem: (N, N, N)) -> Self {
117        Version {
118            major: elem.0.into(),
119            minor: elem.1.into(),
120            revision: elem.2.into(),
121        }
122    }
123}
124
125/// A parse error for Version
126#[derive(Debug)]
127pub enum VersionError {
128    /// Attempted to parse an empty string
129    Empty,
130    /// The string contained too many decimals, contains the number of decimals
131    TooManyDecimals(usize),
132    /// Encountered a num::ParseIntError
133    ParseError(std::num::ParseIntError),
134}
135
136impl std::fmt::Display for VersionError {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
138        match self {
139            VersionError::Empty => write!(f, "VersionError::Empty"),
140            VersionError::TooManyDecimals(d) => {
141                write!(f, "VersionError::TooManyDecimals => found {}", d)
142            }
143            VersionError::ParseError(p) => write!(f, "VersionError::ParseError => {}", p),
144        }
145    }
146}
147
148impl From<std::num::ParseIntError> for VersionError {
149    fn from(e: std::num::ParseIntError) -> Self {
150        Self::ParseError(e)
151    }
152}
153
154impl Into<String> for Version {
155    fn into(self) -> String {
156        format!("{:?}", self)
157    }
158}
159
160impl std::str::FromStr for Version {
161    type Err = VersionError;
162    fn from_str(string: &str) -> Result<Self, Self::Err> {
163        let mut three;
164        #[cfg(feature = "lossy")]
165        {
166            three = string.split(|x| match x {
167                '0'..='9' => false,
168                _ => true,
169            });
170        }
171        #[cfg(not(feature = "lossy"))]
172        {
173            three = string.split('.');
174        }
175        let res: Version;
176        if let Some(major) = three.next() {
177            if let Some(minor) = three.next() {
178                if let Some(revision) = three.next() {
179                    #[cfg(feature = "lossy")]
180                    {
181                        res = Version::from((
182                            major.parse::<u32>()?,
183                            minor.parse().unwrap_or(0),
184                            revision.parse().unwrap_or(0),
185                        ));
186                    }
187                    #[cfg(not(feature = "lossy"))]
188                    {
189                        res = Version::from((
190                            major.parse::<u32>()?,
191                            minor.parse()?,
192                            revision.parse()?,
193                        ));
194                    }
195                } else {
196                    #[cfg(feature = "lossy")]
197                    {
198                        res = Version::from((major.parse::<u32>()?, minor.parse().unwrap_or(0), 0));
199                    }
200                    #[cfg(not(feature = "lossy"))]
201                    {
202                        res = Version::from((major.parse::<u32>()?, minor.parse()?, 0));
203                    }
204                }
205            } else {
206                res = Version::from((major.parse::<u32>()?, 0, 0));
207            }
208        } else {
209            return Err(VersionError::Empty);
210        }
211        #[cfg(not(feature = "lossy"))]
212        if three.next() == None {
213            Ok(res)
214        } else {
215            Err(VersionError::TooManyDecimals(4 + three.count()))
216        }
217        #[cfg(feature = "lossy")]
218        Ok(res)
219    }
220}
221
222impl std::convert::TryFrom<&str> for Version {
223    type Error = VersionError;
224
225    fn try_from(string: &str) -> Result<Self, Self::Error> {
226        std::str::FromStr::from_str(string)
227    }
228}
229
230impl Version {
231    /// Creates a new version
232    pub fn new(major: u32, minor: u32, revision: u32) -> Self {
233        Version {
234            major,
235            minor,
236            revision,
237        }
238    }
239
240    /// Copies the version, updating the major number
241    pub fn with_major(&self, major: u32) -> Self {
242        Self {
243            major: major,
244            minor: self.minor,
245            revision: self.revision,
246        }
247    }
248
249    /// Copies the version, updating the minor number
250    pub fn with_minor(&self, minor: u32) -> Self {
251        Self {
252            major: self.major,
253            minor: minor,
254            revision: self.revision,
255        }
256    }
257
258    /// Copies the version, updating the revision number
259    pub fn with_revision(&self, revision: u32) -> Self {
260        Self {
261            major: self.major,
262            minor: self.minor,
263            revision: revision,
264        }
265    }
266}