version_number/lib.rs
1#![deny(missing_docs)]
2
3//! # version-number
4//!
5//! ## Synopsis
6//!
7//! A crate to represent and parse two- and three-component version numbers of the form `major.minor`,
8//! and `major.minor.patch` respectively. These version numbers are often seen within the Rust
9//! project manifests.
10//!
11//! ## Semver compatibility
12//!
13//! The version numbers accepted by this crate are a subset of semver version numbers,
14//! with the exception of also allowing two component (shorthand) `major.minor` versions.
15//!
16//! For example, `1.0` and `1.0.0` are both accepted by this library, while the former is
17//! rejected by [`semver`].
18//!
19//! In addition [`Version`] does not accept extra labels such as build parameters, which are
20//! an extension of the [`semver`] version number itself.
21//!
22//! In this crate, we call a two component `major.minor` version number a [`BaseVersion`], and
23//! we call a three component `major.minor.patch` version number a [`FullVersion`].
24//!
25//! [`semver`]: https://semver.org/spec/v2.0.0.html
26//! [`Version`]: crate::Version
27//! [`BaseVersion`]: crate::BaseVersion
28//! [`FullVersion`]: crate::FullVersion
29
30use std::fmt;
31use std::str::FromStr;
32
33use crate::parsers::original;
34
35pub use parsers::{BaseVersionParser, FullVersionParser, ParserError, VersionParser};
36pub use version::{BaseVersion, FullVersion};
37
38/// This crate contains multiple parsers.
39///
40/// In general, it's easiest to use the well tested [`parsers::original::Parser`], which is also used
41/// (currently) by [`Version::parse`].
42pub mod parsers;
43
44mod version;
45
46/// Top level errors for version-numbers.
47#[derive(Debug, thiserror::Error)]
48pub enum Error {
49    /// An error which specifies failure to parse a version number.
50    #[error(transparent)]
51    ParserError(#[from] ParserError),
52}
53
54/// A numbered version which is a two-component `major.minor` version number,
55/// or a three-component `major.minor.patch` version number.
56#[derive(Clone, Debug, Hash, Eq, PartialEq)]
57pub enum Version {
58    /// A two-component `major.minor` version.
59    Base(BaseVersion),
60    /// A three-component `major.minor.patch` version.
61    Full(FullVersion),
62}
63
64impl Version {
65    /// Parse a two- or three-component, `major.minor` or `major.minor.patch` respectively,
66    /// version number from a given input.
67    ///
68    /// Returns a [`Error::ParserError`] if it fails to parse.
69    pub fn parse(input: &str) -> Result<Self, Error> {
70        original::Parser::from(input.as_bytes())
71            .parse()
72            .map_err(|e| Error::from(Into::<ParserError>::into(e)))
73    }
74
75    /// Create a new two-component `major.minor` version number.
76    pub fn new_base_version(major: u64, minor: u64) -> Self {
77        Self::Base(BaseVersion { major, minor })
78    }
79
80    /// Create a new three-component `major.minor.patch` version number.
81    pub fn new_full_version(major: u64, minor: u64, patch: u64) -> Self {
82        Self::Full(FullVersion {
83            major,
84            minor,
85            patch,
86        })
87    }
88
89    /// Returns the `major` version component.
90    ///
91    /// Both the two- and three-component version number variants have a major version.
92    /// This is the leading component.
93    pub fn major(&self) -> u64 {
94        match self {
95            Self::Base(inner) => inner.major,
96            Self::Full(inner) => inner.major,
97        }
98    }
99
100    /// Returns the `minor` version component.
101    ///
102    /// Both the two- and three-component version number variants have a minor version.
103    /// This is the middle component.
104    pub fn minor(&self) -> u64 {
105        match self {
106            Self::Base(inner) => inner.minor,
107            Self::Full(inner) => inner.minor,
108        }
109    }
110
111    /// Returns the `patch` version component, if any.
112    ///
113    /// A three component `major.minor.patch` version will return a `Some(<version>)`,
114    /// while a two component `major.minor` version will return `None` instead.
115    ///
116    /// If it exists, it is the last component.
117    pub fn patch(&self) -> Option<u64> {
118        match self {
119            Self::Base(_) => None,
120            Self::Full(inner) => Some(inner.patch),
121        }
122    }
123
124    /// Check of which variant `self` is.
125    pub fn is(&self, variant: Variant) -> bool {
126        match self {
127            Version::Base(_) => matches!(variant, Variant::Base),
128            Version::Full(_) => matches!(variant, Variant::Full),
129        }
130    }
131
132    /// Map a [`Version`] to `U`.
133    ///
134    /// # Example
135    ///
136    /// ```
137    /// use version_number::{BaseVersion, FullVersion, Version};
138    ///
139    /// // 🧑🔬
140    /// fn invert_version(v: Version) -> Version {
141    ///     match v {
142    ///         Version::Base(base) => Version::Base(BaseVersion::new(base.minor, base.major)),
143    ///         Version::Full(full) => Version::Full(FullVersion::new(full.patch, full.minor, full.major))
144    ///     }
145    /// }
146    ///
147    /// let base_example = Version::Base(BaseVersion::new(1, 2));
148    /// let full_example = Version::Full(FullVersion::new(1, 2, 3));
149    ///
150    /// assert_eq!(base_example.map(invert_version), Version::Base(BaseVersion::new(2, 1)));
151    /// assert_eq!(full_example.map(invert_version), Version::Full(FullVersion::new(3, 2, 1)));
152    /// ```
153    #[inline]
154    pub fn map<U, F>(self, fun: F) -> U
155    where
156        F: FnOnce(Self) -> U,
157    {
158        fun(self)
159    }
160
161    /// Map over the `major` version component of the [`Version`].
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// use version_number::{BaseVersion, FullVersion, Version};
167    ///
168    /// let base_example = Version::Base(BaseVersion::new(0, 0));
169    /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
170    ///
171    /// assert_eq!(base_example.map_major(|a| a + 1), Version::Base(BaseVersion::new(1, 0)));
172    /// assert_eq!(full_example.map_major(|a| a + 1), Version::Full(FullVersion::new(1, 0, 0)));
173    /// ```
174    #[inline]
175    pub fn map_major<F>(self, fun: F) -> Self
176    where
177        F: FnOnce(u64) -> u64,
178    {
179        self.map(|v| match v {
180            Self::Base(base) => Version::Base(BaseVersion::new(fun(base.major), base.minor)),
181            Self::Full(full) => {
182                Version::Full(FullVersion::new(fun(full.major), full.minor, full.patch))
183            }
184        })
185    }
186
187    /// Map over the `minor` version component of the [`Version`].
188    ///
189    /// # Example
190    ///
191    /// ```
192    /// use version_number::{BaseVersion, FullVersion, Version};
193    ///
194    /// let base_example = Version::Base(BaseVersion::new(0, 0));
195    /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
196    ///
197    /// assert_eq!(base_example.map_minor(|a| a + 1), Version::Base(BaseVersion::new(0, 1)));
198    /// assert_eq!(full_example.map_minor(|a| a + 1), Version::Full(FullVersion::new(0, 1, 0)));
199    /// ```
200    #[inline]
201    pub fn map_minor<F>(self, fun: F) -> Self
202    where
203        F: FnOnce(u64) -> u64,
204    {
205        self.map(|v| match v {
206            Self::Base(base) => Version::Base(BaseVersion::new(base.major, fun(base.minor))),
207            Self::Full(full) => {
208                Version::Full(FullVersion::new(full.major, fun(full.minor), full.patch))
209            }
210        })
211    }
212
213    /// Map over the `patch` version component of the [`Version`].
214    /// If no `patch` version exists (in case the [`Version`] consists of two components),
215    /// then the original version is returned.
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// use version_number::{BaseVersion, FullVersion, Version};
221    ///
222    /// let base_example = Version::Base(BaseVersion::new(0, 0));
223    /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
224    ///
225    /// assert_eq!(base_example.map_patch(|a| a + 1), Version::Base(BaseVersion::new(0, 0)));
226    /// assert_eq!(full_example.map_patch(|a| a + 1), Version::Full(FullVersion::new(0, 0, 1)));
227    /// ```
228    #[inline]
229    pub fn map_patch<F>(self, fun: F) -> Self
230    where
231        F: FnOnce(u64) -> u64,
232    {
233        self.map(|v| match v {
234            Self::Base(base) => Version::Base(BaseVersion::new(base.major, base.minor)),
235            Self::Full(full) => {
236                Version::Full(FullVersion::new(full.major, full.minor, fun(full.patch)))
237            }
238        })
239    }
240}
241
242impl FromStr for Version {
243    type Err = Error;
244
245    fn from_str(input: &str) -> Result<Self, Error> {
246        original::Parser::from_slice(input.as_bytes())
247            .parse()
248            .map_err(|e| Error::from(Into::<ParserError>::into(e)))
249    }
250}
251
252impl fmt::Display for Version {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        match self {
255            Self::Base(inner) => fmt::Display::fmt(&inner, f),
256            Self::Full(inner) => fmt::Display::fmt(&inner, f),
257        }
258    }
259}
260
261impl From<(u64, u64)> for Version {
262    fn from(tuple: (u64, u64)) -> Self {
263        Self::Base(BaseVersion::from(tuple))
264    }
265}
266
267impl From<(u64, u64, u64)> for Version {
268    fn from(tuple: (u64, u64, u64)) -> Self {
269        Self::Full(FullVersion::from(tuple))
270    }
271}
272
273/// Type used to indicate which variant of a [`Version`] is used.
274/// The options are [`Base`] for [`Version::Base`], and [`Full`] for [`Version::Full`].
275///
276/// [`Version`]: crate::Version
277/// [`Base`]: crate::Variant::Base
278/// [`Version::Base`]: crate::Version::Base
279/// [`Full`]: crate::Variant::Full
280/// [`Version::Full`]: crate::Version::Full
281#[derive(Copy, Clone, Debug)]
282pub enum Variant {
283    /// Indicates a [`Version::Base`] is used.
284    ///
285    /// [`Version::Base`]: crate::Version::Base
286    Base,
287    /// Indicates a [`Version::Full`] is used.
288    ///
289    /// [`Version::Full`]: crate::Version::Full
290    Full,
291}
292
293#[cfg(test)]
294mod tests {
295    use crate::{BaseVersion, FullVersion, Variant, Version};
296
297    #[test]
298    fn is_base_variant() {
299        let version = Version::Base(BaseVersion::new(0, 0));
300
301        assert!(version.is(Variant::Base));
302        assert!(!version.is(Variant::Full));
303    }
304
305    #[test]
306    fn is_full_variant() {
307        let version = Version::Full(FullVersion::new(0, 0, 0));
308
309        assert!(version.is(Variant::Full));
310        assert!(!version.is(Variant::Base));
311    }
312
313    #[test]
314    fn map() {
315        let version = Version::Base(BaseVersion::new(0, 0));
316
317        let mapped = version.map(|v| match v {
318            Version::Base(base) => Version::Full(FullVersion::new(base.major, base.minor, 999)),
319            v => v,
320        });
321
322        assert_eq!(mapped, Version::Full(FullVersion::new(0, 0, 999)));
323    }
324
325    #[yare::parameterized(
326        base = { Version::Base(BaseVersion::new(0, 0)) },
327        full = { Version::Full(FullVersion::new(0, 0, 0)) },
328    )]
329    fn map_major(version: Version) {
330        let mapped = version.map_major(|_v| 999);
331
332        assert_eq!(mapped.major(), 999);
333    }
334
335    #[yare::parameterized(
336        base = { Version::Base(BaseVersion::new(0, 0)) },
337        full = { Version::Full(FullVersion::new(0, 0, 0)) },
338    )]
339    fn map_minor(version: Version) {
340        let mapped = version.map_minor(|_v| 999);
341
342        assert_eq!(mapped.minor(), 999);
343    }
344
345    #[test]
346    fn map_patch_base() {
347        let version = Version::Base(BaseVersion::new(0, 0));
348        let mapped = version.map_patch(|_v| 999);
349
350        assert!(mapped.patch().is_none());
351    }
352
353    #[test]
354    fn map_patch_full() {
355        let version = Version::Full(FullVersion::new(0, 0, 0));
356        let mapped = version.map_patch(|_v| 999);
357
358        assert_eq!(mapped.patch().unwrap(), 999);
359    }
360}