semantic_version.rs

 1//! Constructs for working with [semantic versions](https://semver.org/).
 2
 3#![deny(missing_docs)]
 4
 5use std::{
 6    fmt::{self, Display},
 7    str::FromStr,
 8};
 9
10use anyhow::{Context as _, Result};
11use serde::{Deserialize, Serialize, de::Error};
12
13/// A [semantic version](https://semver.org/) number.
14#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
15pub struct SemanticVersion {
16    major: usize,
17    minor: usize,
18    patch: usize,
19}
20
21impl SemanticVersion {
22    /// Returns a new [`SemanticVersion`] from the given components.
23    pub const fn new(major: usize, minor: usize, patch: usize) -> Self {
24        Self {
25            major,
26            minor,
27            patch,
28        }
29    }
30
31    /// Returns the major version number.
32    #[inline(always)]
33    pub fn major(&self) -> usize {
34        self.major
35    }
36
37    /// Returns the minor version number.
38    #[inline(always)]
39    pub fn minor(&self) -> usize {
40        self.minor
41    }
42
43    /// Returns the patch version number.
44    #[inline(always)]
45    pub fn patch(&self) -> usize {
46        self.patch
47    }
48}
49
50impl FromStr for SemanticVersion {
51    type Err = anyhow::Error;
52
53    fn from_str(s: &str) -> Result<Self> {
54        let mut components = s.trim().split('.');
55        let major = components
56            .next()
57            .context("missing major version number")?
58            .parse()?;
59        let minor = components
60            .next()
61            .context("missing minor version number")?
62            .parse()?;
63        let patch = components
64            .next()
65            .context("missing patch version number")?
66            .parse()?;
67        Ok(Self {
68            major,
69            minor,
70            patch,
71        })
72    }
73}
74
75impl Display for SemanticVersion {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
78    }
79}
80
81impl Serialize for SemanticVersion {
82    fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
83    where
84        S: serde::Serializer,
85    {
86        serializer.serialize_str(&self.to_string())
87    }
88}
89
90impl<'de> Deserialize<'de> for SemanticVersion {
91    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92    where
93        D: serde::Deserializer<'de>,
94    {
95        let string = String::deserialize(deserializer)?;
96        Self::from_str(&string)
97            .map_err(|_| Error::custom(format!("Invalid version string \"{string}\"")))
98    }
99}