loose_semver/
version.rs

1use super::parser::parse_version;
2use core::fmt;
3use std::cmp::Ordering;
4use std::fmt::{Display, Write};
5use std::str::FromStr;
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
8pub enum VersionState {
9    Alpha(String),
10    Beta(String),
11    ReleaseCandidate(String),
12    Release,
13}
14
15impl Default for VersionState {
16    fn default() -> Self {
17        Self::Release
18    }
19}
20
21impl fmt::Display for VersionState {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::Alpha(v) => write!(f, "alpha{v}"),
25            Self::Beta(v) => write!(f, "beta{v}"),
26            Self::ReleaseCandidate(v) => write!(f, "rc{v}"),
27            Self::Release => write!(f, ""),
28        }
29    }
30}
31
32/// Definitions of version parts.
33///
34/// ```txt
35/// 1.2.34(-)rc(.)5
36/// | | |    |___ Pre
37/// | | |________ Patch
38/// | |__________ Minor
39/// |____________ Major
40/// ```
41#[allow(unused)]
42pub enum VersionPart {
43    Major,
44    Minor,
45    Patch,
46    Pre,
47}
48
49/// Semantic Version representation.
50///
51/// # Examples
52/// Basic usage:
53/// ```
54/// # use loose_semver::Version;
55/// let v: Version = "1.2.3rc4".parse().unwrap();
56/// ```
57#[derive(Debug, Clone, Default, PartialEq, Eq)]
58pub struct Version {
59    pub major: usize,
60    pub minor: Option<usize>,
61    pub patch: Option<usize>,
62    pub pre: Option<VersionState>,
63}
64
65impl Version {
66    /// Returns true if the version defines a stable
67    /// release (not suffixed with an `alpha`, `beta` or `rc`
68    /// version part).
69    ///
70    /// # Example
71    /// ```
72    /// # use loose_semver::{Version, VersionPart};
73    /// let version: Version = "1.2".parse().unwrap();
74    /// assert!(version.is_stable());
75    ///
76    /// let version: Version = "1.3rc1".parse().unwrap();
77    /// assert!(!version.is_stable());
78    /// ```
79    pub fn is_stable(&self) -> bool {
80        match &self.pre {
81            None => true,
82            Some(x) => matches!(x, VersionState::Release),
83        }
84    }
85
86    /// Returns true if the current version covers
87    /// the given `other` version.
88    ///
89    /// # Example
90    /// ```
91    /// # use loose_semver::{Version, VersionPart};
92    /// let a: Version = "1.2".parse().unwrap();
93    /// let b: Version = "1.2".parse().unwrap();
94    /// assert!(a.covers(&b));
95    ///
96    /// let a: Version = "1".parse().unwrap();
97    /// let b: Version = "1.2.1".parse().unwrap();
98    /// assert!(a.covers(&b));
99    ///
100    /// let a: Version = "1.3".parse().unwrap();
101    /// let b: Version = "1.3.7".parse().unwrap();
102    /// assert!(a.covers(&b));
103    ///
104    /// let a: Version = "1.2.3".parse().unwrap();
105    /// let b: Version = "1.2.3rc1".parse().unwrap();
106    /// assert!(a.covers(&b));
107    ///
108    /// let a: Version = "1.3".parse().unwrap();
109    /// let b: Version = "1.2.1".parse().unwrap();
110    /// assert!(!a.covers(&b));
111    /// ```
112    pub fn covers(&self, other: &Version) -> bool {
113        if self.major != other.major {
114            return false;
115        }
116
117        match self.minor {
118            Some(x) if x != other.minor.unwrap_or_default() => return false,
119            _ => {}
120        };
121
122        match self.patch {
123            Some(x) if x != other.patch.unwrap_or_default() => return false,
124            _ => {}
125        };
126
127        match &self.pre {
128            Some(x) if x != &other.pre.clone().unwrap_or_default() => return false,
129            _ => {}
130        };
131
132        true
133    }
134
135    /// Returns a copy of the [`Version`] with the part after the
136    /// given [`VersionPart`] removed.
137    ///
138    /// # Example
139    /// ```
140    /// # use loose_semver::{Version, VersionPart};
141    /// let a: Version = "1.2.3rc1".parse().unwrap();
142    /// let b: Version = "1".parse().unwrap();
143    /// assert_eq!(a.strip_after(VersionPart::Major), b);
144    ///
145    /// let a: Version = "1.2.3rc1".parse().unwrap();
146    /// let b: Version = "1.2".parse().unwrap();
147    /// assert_eq!(a.strip_after(VersionPart::Minor), b);
148    ///
149    /// let a: Version = "1.2.3rc1".parse().unwrap();
150    /// let b: Version = "1.2.3".parse().unwrap();
151    /// assert_eq!(a.strip_after(VersionPart::Patch), b);
152    /// ```
153    pub fn strip_after(&self, part: VersionPart) -> Self {
154        let s = self.clone();
155        match part {
156            VersionPart::Major => Self {
157                minor: None,
158                patch: None,
159                pre: None,
160                ..s
161            },
162            VersionPart::Minor => Self {
163                patch: None,
164                pre: None,
165                ..s
166            },
167            VersionPart::Patch => Self { pre: None, ..s },
168            VersionPart::Pre => s,
169        }
170    }
171}
172
173impl FromStr for Version {
174    type Err = anyhow::Error;
175
176    fn from_str(s: &str) -> Result<Self, Self::Err> {
177        parse_version(s)
178    }
179}
180
181impl Ord for Version {
182    fn cmp(&self, other: &Self) -> Ordering {
183        if self == other {
184            return Ordering::Equal;
185        }
186
187        match self.major.cmp(&other.major) {
188            Ordering::Equal => {}
189            ord => return ord,
190        }
191
192        match self
193            .minor
194            .unwrap_or_default()
195            .cmp(&other.minor.unwrap_or_default())
196        {
197            Ordering::Equal => {}
198            ord => return ord,
199        }
200
201        match self
202            .patch
203            .unwrap_or_default()
204            .cmp(&other.patch.unwrap_or_default())
205        {
206            Ordering::Equal => {}
207            ord => return ord,
208        }
209
210        self.pre
211            .clone()
212            .unwrap_or_default()
213            .cmp(&other.pre.clone().unwrap_or_default())
214    }
215}
216
217impl PartialOrd for Version {
218    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219        Some(self.cmp(other))
220    }
221}
222
223impl Display for Version {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        f.write_str(&self.major.to_string())?;
226
227        if let Some(minor) = &self.minor {
228            f.write_char('.')?;
229            f.write_str(&minor.to_string())?;
230        }
231
232        if let Some(patch) = &self.patch {
233            f.write_char('.')?;
234            f.write_str(&patch.to_string())?;
235        }
236
237        if let Some(pre) = &self.pre {
238            f.write_str(&pre.to_string())?;
239        }
240
241        Ok(())
242    }
243}
244
245#[cfg(test)]
246mod test {
247    use super::*;
248
249    #[test]
250    fn parse_stable() {
251        assert_eq!(
252            Version::from_str("1").unwrap(),
253            Version {
254                major: 1,
255                ..Default::default()
256            }
257        );
258
259        assert_eq!(
260            Version::from_str("1.2").unwrap(),
261            Version {
262                major: 1,
263                minor: Some(2),
264                ..Default::default()
265            }
266        );
267
268        assert_eq!(
269            Version::from_str("1.2.345").unwrap(),
270            Version {
271                major: 1,
272                minor: Some(2),
273                patch: Some(345),
274                ..Default::default()
275            }
276        );
277
278        assert_eq!(
279            Version::from_str("v1.2.345").unwrap(),
280            Version {
281                major: 1,
282                minor: Some(2),
283                patch: Some(345),
284                ..Default::default()
285            }
286        );
287
288        assert_eq!(
289            Version::from_str("V1.2.345").unwrap(),
290            Version {
291                major: 1,
292                minor: Some(2),
293                patch: Some(345),
294                ..Default::default()
295            }
296        );
297    }
298
299    #[test]
300    fn parse_unstable() {
301        assert_eq!(
302            Version::from_str("1rc1").unwrap(),
303            Version {
304                major: 1,
305                pre: Some(VersionState::ReleaseCandidate("1".into())),
306                ..Default::default()
307            }
308        );
309
310        assert_eq!(
311            Version::from_str("1-rc1").unwrap(),
312            Version {
313                major: 1,
314                pre: Some(VersionState::ReleaseCandidate("1".into())),
315                ..Default::default()
316            }
317        );
318
319        assert_eq!(
320            Version::from_str("1-rc.1").unwrap(),
321            Version {
322                major: 1,
323                pre: Some(VersionState::ReleaseCandidate("1".into())),
324                ..Default::default()
325            }
326        );
327
328        assert_eq!(
329            Version::from_str("1.2beta34").unwrap(),
330            Version {
331                major: 1,
332                minor: Some(2),
333                pre: Some(VersionState::Beta("34".into())),
334                ..Default::default()
335            }
336        );
337
338        assert_eq!(
339            Version::from_str("1.2-beta34").unwrap(),
340            Version {
341                major: 1,
342                minor: Some(2),
343                pre: Some(VersionState::Beta("34".into())),
344                ..Default::default()
345            }
346        );
347
348        assert_eq!(
349            Version::from_str("1.2-beta.3.4").unwrap(),
350            Version {
351                major: 1,
352                minor: Some(2),
353                pre: Some(VersionState::Beta("3.4".into())),
354                ..Default::default()
355            }
356        );
357
358        assert_eq!(
359            Version::from_str("1.2.345alpha6.7.8").unwrap(),
360            Version {
361                major: 1,
362                minor: Some(2),
363                patch: Some(345),
364                pre: Some(VersionState::Alpha("6.7.8".into()))
365            }
366        );
367
368        assert_eq!(
369            Version::from_str("1.2.345-alpha6.7.8").unwrap(),
370            Version {
371                major: 1,
372                minor: Some(2),
373                patch: Some(345),
374                pre: Some(VersionState::Alpha("6.7.8".into()))
375            }
376        );
377
378        assert_eq!(
379            Version::from_str("1.2.345-alpha.6.7.8").unwrap(),
380            Version {
381                major: 1,
382                minor: Some(2),
383                patch: Some(345),
384                pre: Some(VersionState::Alpha("6.7.8".into()))
385            }
386        );
387    }
388
389    #[test]
390    fn ord() {
391        assert!(Version::from_str("2").unwrap() > Version::from_str("1").unwrap());
392        assert!(Version::from_str("2.1").unwrap() > Version::from_str("1.3").unwrap());
393        assert!(Version::from_str("1.4").unwrap() > Version::from_str("1.3").unwrap());
394        assert!(Version::from_str("1.2.3").unwrap() > Version::from_str("1.2").unwrap());
395        assert!(Version::from_str("1.2.3").unwrap() > Version::from_str("1.2.2").unwrap());
396        assert!(Version::from_str("1").unwrap() > Version::from_str("1rc1").unwrap());
397        assert!(Version::from_str("1.2rc1").unwrap() > Version::from_str("1.2beta1").unwrap());
398        assert!(Version::from_str("1.2beta2").unwrap() > Version::from_str("1.2beta1").unwrap());
399        assert!(Version::from_str("2").unwrap() > Version::from_str("1rc2").unwrap());
400    }
401
402    #[test]
403    fn is_stable() {
404        assert!(Version::from_str("1").unwrap().is_stable());
405        assert!(Version::from_str("1.1").unwrap().is_stable());
406        assert!(Version::from_str("1.1.3").unwrap().is_stable());
407
408        assert!(!Version::from_str("1alpha1").unwrap().is_stable());
409        assert!(!Version::from_str("1.2beta2").unwrap().is_stable());
410        assert!(!Version::from_str("1.2.3rc3").unwrap().is_stable());
411    }
412
413    #[test]
414    fn covers() {
415        let a: Version = "1.2".parse().unwrap();
416        let b: Version = "1.2".parse().unwrap();
417        assert!(a.covers(&b));
418
419        let a: Version = "1".parse().unwrap();
420        let b: Version = "1.2.1".parse().unwrap();
421        assert!(a.covers(&b));
422
423        let a: Version = "1.3".parse().unwrap();
424        let b: Version = "1.3.7".parse().unwrap();
425        assert!(a.covers(&b));
426
427        let a: Version = "1.2.3".parse().unwrap();
428        let b: Version = "1.2.3rc1".parse().unwrap();
429        assert!(a.covers(&b));
430
431        let a: Version = "1.3".parse().unwrap();
432        let b: Version = "1.2.1".parse().unwrap();
433        assert!(!a.covers(&b));
434    }
435
436    #[test]
437    fn strip_after() {
438        let a: Version = "1.2.3rc1".parse().unwrap();
439        let b: Version = "1".parse().unwrap();
440        assert_eq!(a.strip_after(VersionPart::Major), b);
441
442        let a: Version = "1.2.3rc1".parse().unwrap();
443        let b: Version = "1.2".parse().unwrap();
444        assert_eq!(a.strip_after(VersionPart::Minor), b);
445
446        let a: Version = "1.2.3rc1".parse().unwrap();
447        let b: Version = "1.2.3".parse().unwrap();
448        assert_eq!(a.strip_after(VersionPart::Patch), b);
449
450        let a: Version = "1.2.3rc1".parse().unwrap();
451        let b: Version = "1.2.3rc1".parse().unwrap();
452        assert_eq!(a.strip_after(VersionPart::Pre), b);
453    }
454}