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#[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}