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
73macro_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#[derive(Debug)]
127pub enum VersionError {
128 Empty,
130 TooManyDecimals(usize),
132 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 pub fn new(major: u32, minor: u32, revision: u32) -> Self {
233 Version {
234 major,
235 minor,
236 revision,
237 }
238 }
239
240 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 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 pub fn with_revision(&self, revision: u32) -> Self {
260 Self {
261 major: self.major,
262 minor: self.minor,
263 revision: revision,
264 }
265 }
266}