knope_versioning/semver/
mod.rs

1use std::{cmp::Ordering, fmt::Display, str::FromStr};
2
3#[cfg(feature = "miette")]
4use miette::Diagnostic;
5pub use package_versions::{PackageVersions, PreReleaseNotFound};
6pub use rule::{Rule, Stable as StableRule};
7use serde::{Deserialize, Serialize};
8
9mod package_versions;
10mod prerelease_map;
11mod rule;
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq)]
14pub enum Version {
15    Stable(StableVersion),
16    Pre(PreVersion),
17}
18
19impl Version {
20    #[must_use]
21    pub const fn stable_component(&self) -> StableVersion {
22        match self {
23            Self::Stable(stable) => *stable,
24            Self::Pre(pre) => pre.stable_component,
25        }
26    }
27
28    #[must_use]
29    pub const fn is_prerelease(&self) -> bool {
30        matches!(self, Version::Pre(_))
31    }
32}
33
34impl Version {
35    #[must_use]
36    pub fn new(major: u64, minor: u64, patch: u64, pre: Option<Prerelease>) -> Self {
37        let stable = StableVersion {
38            major,
39            minor,
40            patch,
41        };
42        match pre {
43            Some(pre) => Self::Pre(PreVersion {
44                stable_component: stable,
45                pre_component: pre,
46            }),
47            None => Self::Stable(stable),
48        }
49    }
50}
51
52impl From<StableVersion> for Version {
53    fn from(stable: StableVersion) -> Self {
54        Self::Stable(stable)
55    }
56}
57
58impl<'de> Deserialize<'de> for Version {
59    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
60        let version = String::deserialize(deserializer)?;
61        Version::from_str(&version).map_err(serde::de::Error::custom)
62    }
63}
64
65impl Serialize for Version {
66    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
67        let version = self.to_string();
68        serializer.serialize_str(&version)
69    }
70}
71
72#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
73pub struct StableVersion {
74    pub(crate) major: u64,
75    pub(crate) minor: u64,
76    pub(crate) patch: u64,
77}
78
79impl StableVersion {
80    #[must_use]
81    pub(crate) const fn increment_major(self) -> Self {
82        Self {
83            major: self.major + 1,
84            minor: 0,
85            patch: 0,
86        }
87    }
88
89    #[must_use]
90    pub(crate) const fn increment_minor(self) -> Self {
91        Self {
92            major: self.major,
93            minor: self.minor + 1,
94            patch: 0,
95        }
96    }
97
98    #[must_use]
99    pub(crate) const fn increment_patch(self) -> Self {
100        Self {
101            major: self.major,
102            minor: self.minor,
103            patch: self.patch + 1,
104        }
105    }
106}
107
108impl Ord for StableVersion {
109    fn cmp(&self, other: &Self) -> Ordering {
110        self.major
111            .cmp(&other.major)
112            .then_with(|| self.minor.cmp(&other.minor))
113            .then_with(|| self.patch.cmp(&other.patch))
114    }
115}
116
117impl PartialOrd for StableVersion {
118    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
119        Some(self.cmp(other))
120    }
121}
122
123impl Display for StableVersion {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(
126            f,
127            "{major}.{minor}.{patch}",
128            major = self.major,
129            minor = self.minor,
130            patch = self.patch
131        )
132    }
133}
134
135#[derive(Clone, Debug, Eq, Hash, PartialEq)]
136pub struct PreVersion {
137    pub stable_component: StableVersion,
138    pub pre_component: Prerelease,
139}
140
141impl Ord for Version {
142    fn cmp(&self, other: &Self) -> Ordering {
143        match self.stable_component().cmp(&other.stable_component()) {
144            Ordering::Equal => match (self, other) {
145                (Self::Stable(_), Self::Stable(_)) => Ordering::Equal,
146                (Self::Stable(_), Self::Pre(_)) => Ordering::Greater,
147                (Self::Pre(_), Self::Stable(_)) => Ordering::Less,
148                (Self::Pre(pre), Self::Pre(other_pre)) => {
149                    pre.pre_component.cmp(&other_pre.pre_component)
150                }
151            },
152            ordering => ordering,
153        }
154    }
155}
156
157impl PartialOrd for Version {
158    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
159        Some(self.cmp(other))
160    }
161}
162
163impl FromStr for Version {
164    type Err = Error;
165
166    fn from_str(s: &str) -> Result<Self, Self::Err> {
167        let (version, pre) = s
168            .split_once('-')
169            .map_or((s, None), |(version, pre)| (version, Some(pre)));
170        let version_parts: [u64; 3] = version
171            .split('.')
172            .map(|part| part.parse::<u64>().map_err(|err| Error(err.to_string())))
173            .collect::<Result<Vec<_>, _>>()?
174            .try_into()
175            .map_err(|_| Error("Version must have exactly 3 parts".to_string()))?;
176        let stable = StableVersion {
177            major: version_parts[0],
178            minor: version_parts[1],
179            patch: version_parts[2],
180        };
181        if let Some(pre) = pre {
182            Ok(Self::Pre(PreVersion {
183                stable_component: stable,
184                pre_component: Prerelease::from_str(pre)?,
185            }))
186        } else {
187            Ok(Self::Stable(stable))
188        }
189    }
190}
191
192#[derive(Debug, thiserror::Error)]
193#[cfg_attr(feature = "miette", derive(Diagnostic))]
194#[error("Found invalid semantic version {0}")]
195#[cfg_attr(
196    feature = "miette",
197    diagnostic(
198        code(version),
199        help("The version must be a valid Semantic Version"),
200        url("https://knope.tech/reference/concepts/semantic-versioning")
201    )
202)]
203pub struct Error(String);
204
205impl Display for Version {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        match self {
208            Self::Stable(stable) => write!(f, "{stable}"),
209            Self::Pre(PreVersion {
210                stable_component,
211                pre_component,
212            }) => write!(f, "{stable_component}-{pre_component}",),
213        }
214    }
215}
216
217#[derive(Clone, Debug, Eq, Hash, PartialEq)]
218pub struct Prerelease {
219    pub label: Label,
220    pub version: u64,
221}
222
223impl Display for Prerelease {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        write!(f, "{}.{}", self.label, self.version)
226    }
227}
228
229impl FromStr for Prerelease {
230    type Err = Error;
231
232    fn from_str(s: &str) -> Result<Self, Self::Err> {
233        let (label, version) = s
234            .split_once('.')
235            .ok_or_else(|| Error("Invalid prerelease".to_string()))?;
236        Ok(Self {
237            label: Label(String::from(label)),
238            version: version
239                .parse::<u64>()
240                .map_err(|err| Error(err.to_string()))?,
241        })
242    }
243}
244
245impl Ord for Prerelease {
246    fn cmp(&self, other: &Self) -> Ordering {
247        self.label
248            .cmp(&other.label)
249            .then(self.version.cmp(&other.version))
250    }
251}
252
253impl PartialOrd for Prerelease {
254    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
255        Some(self.cmp(other))
256    }
257}
258
259impl Prerelease {
260    #[must_use]
261    pub fn new(label: Label, version: u64) -> Self {
262        Self { label, version }
263    }
264}
265
266/// The label component of a Prerelease (e.g., "alpha" in "1.0.0-alpha.1").
267#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
268#[repr(transparent)]
269pub struct Label(pub String);
270
271impl Display for Label {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        write!(f, "{}", self.0)
274    }
275}
276
277impl From<&str> for Label {
278    fn from(s: &str) -> Self {
279        Self(s.to_string())
280    }
281}