northstar_runtime/common/
version.rs

1use serde::{Deserialize, Serialize};
2use std::{
3    cmp::Ordering,
4    fmt::{self, Display},
5    str::FromStr,
6};
7use thiserror::Error;
8
9/// Parsing error
10#[derive(Error, Debug)]
11pub struct ParseError {
12    #[from]
13    source: semver::Error,
14}
15
16impl fmt::Display for ParseError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        write!(f, "{}", self.source)
19    }
20}
21
22/// Container version
23#[derive(Clone, Eq, PartialEq, Hash, Debug)]
24pub struct Version {
25    /// Major
26    pub major: u64,
27    /// Minor
28    pub minor: u64,
29    /// Patch
30    pub patch: u64,
31}
32
33impl Version {
34    /// Construct a new version
35    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
36        Self {
37            major,
38            minor,
39            patch,
40        }
41    }
42
43    /// Parse a string into a version
44    pub fn parse(version: &str) -> Result<Self, ParseError> {
45        semver::Version::parse(version)
46            .map(|ref version| version.into())
47            .map_err(|source| ParseError { source })
48    }
49}
50
51impl fmt::Display for Version {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
54    }
55}
56
57impl From<&semver::Version> for Version {
58    fn from(version: &semver::Version) -> Self {
59        Self {
60            major: version.major,
61            minor: version.minor,
62            patch: version.patch,
63        }
64    }
65}
66
67impl From<&Version> for semver::Version {
68    fn from(version: &Version) -> Self {
69        semver::Version::new(version.major, version.minor, version.patch)
70    }
71}
72
73impl<T> From<(T, T, T)> for Version
74where
75    T: Into<u64>,
76{
77    fn from((major, minor, patch): (T, T, T)) -> Self {
78        Self {
79            major: major.into(),
80            minor: minor.into(),
81            patch: patch.into(),
82        }
83    }
84}
85
86impl FromStr for Version {
87    type Err = ParseError;
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        semver::Version::parse(s)
91            .map(|ref version| version.into())
92            .map_err(|source| ParseError { source })
93    }
94}
95
96impl Serialize for Version {
97    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98    where
99        S: serde::Serializer,
100    {
101        serializer.serialize_str(&self.to_string())
102    }
103}
104
105impl<'de> Deserialize<'de> for Version {
106    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107    where
108        D: serde::Deserializer<'de>,
109    {
110        let s = String::deserialize(deserializer)?;
111        Version::from_str(&s).map_err(serde::de::Error::custom)
112    }
113}
114
115impl PartialOrd for Version {
116    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
117        Some(self.cmp(other))
118    }
119}
120
121impl Ord for Version {
122    fn cmp(&self, other: &Self) -> Ordering {
123        if self.major > other.major {
124            Ordering::Greater
125        } else if self.major < other.major {
126            Ordering::Less
127        } else if self.minor > other.minor {
128            Ordering::Greater
129        } else if self.minor < other.minor {
130            Ordering::Less
131        } else if self.patch > other.patch {
132            Ordering::Greater
133        } else if self.patch < other.patch {
134            Ordering::Less
135        } else {
136            Ordering::Equal
137        }
138    }
139}
140
141/// Container version requirement
142#[derive(Clone, Eq, PartialEq, Hash, Debug)]
143pub struct VersionReq {
144    inner: semver::VersionReq,
145}
146
147impl VersionReq {
148    /// Parse a string into a version requirement
149    pub fn parse(text: &str) -> Result<VersionReq, ParseError> {
150        Ok(VersionReq {
151            inner: semver::VersionReq::parse(text)?,
152        })
153    }
154
155    /// Check whether the given version satisfies this requirement
156    pub fn matches(&self, version: &Version) -> bool {
157        self.inner.matches(&semver::Version::from(version))
158    }
159}
160
161impl FromStr for VersionReq {
162    type Err = ParseError;
163
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        Ok(VersionReq {
166            inner: semver::VersionReq::from_str(s)?,
167        })
168    }
169}
170
171impl Display for VersionReq {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        write!(f, "{}", self.inner)
174    }
175}
176
177impl Serialize for VersionReq {
178    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179    where
180        S: serde::Serializer,
181    {
182        serializer.serialize_str(&self.inner.to_string())
183    }
184}
185
186impl<'de> Deserialize<'de> for VersionReq {
187    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
188    where
189        D: serde::Deserializer<'de>,
190    {
191        let s = String::deserialize(deserializer)?;
192        VersionReq::parse(&s).map_err(serde::de::Error::custom)
193    }
194}
195
196#[test]
197fn version() -> anyhow::Result<()> {
198    let v1 = Version::parse("1.0.0")?;
199    let v2 = Version::parse("2.0.0")?;
200    let v3 = Version::parse("3.0.0")?;
201    assert!(v2 > v1);
202    assert!(v2 < v3);
203    let v1_1 = Version::parse("1.1.0")?;
204    assert!(v1_1 > v1);
205    let v1_1_1 = Version::parse("1.1.1")?;
206    assert!(v1_1_1 > v1_1);
207    Ok(())
208}