1use serde::{Deserialize, Serialize};
2use std::cmp::Ordering;
3use std::fmt;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct Version {
7 pub major: u32,
8 pub minor: u32,
9 pub patch: u32,
10}
11
12impl Version {
13 #[allow(dead_code)]
14 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
15 Self {
16 major,
17 minor,
18 patch,
19 }
20 }
21
22 pub fn parse(s: &str) -> Result<Self, String> {
23 let parts: Vec<&str> = s.trim().split('.').collect();
24 if parts.len() != 3 {
25 return Err(format!(
26 "Invalid version format: {}. Expected: MAJOR.MINOR.PATCH",
27 s
28 ));
29 }
30 let major = parts[0]
31 .parse::<u32>()
32 .map_err(|e| format!("Invalid major: {}", e))?;
33 let minor = parts[1]
34 .parse::<u32>()
35 .map_err(|e| format!("Invalid minor: {}", e))?;
36 let patch = parts[2]
37 .parse::<u32>()
38 .map_err(|e| format!("Invalid patch: {}", e))?;
39 Ok(Self::new(major, minor, patch))
40 }
41
42 #[allow(dead_code)]
43 pub fn bump_major(&self) -> Self {
44 Self::new(self.major + 1, 0, 0)
45 }
46
47 #[allow(dead_code)]
48 pub fn bump_minor(&self) -> Self {
49 Self::new(self.major, self.minor + 1, 0)
50 }
51
52 #[allow(dead_code)]
53 pub fn bump_patch(&self) -> Self {
54 Self::new(self.major, self.minor, self.patch + 1)
55 }
56}
57
58impl fmt::Display for Version {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
61 }
62}
63
64impl PartialOrd for Version {
65 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
66 Some(self.cmp(other))
67 }
68}
69
70impl Ord for Version {
71 fn cmp(&self, other: &Self) -> Ordering {
72 self.major
73 .cmp(&other.major)
74 .then_with(|| self.minor.cmp(&other.minor))
75 .then_with(|| self.patch.cmp(&other.patch))
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum VersionReq {
82 Exact(Version),
84 Latest,
86 Caret(Version),
88 Tilde(Version),
90 GreaterThan(Version),
92 GreaterEq(Version),
94 LessThan(Version),
96 LessEq(Version),
98 Range {
100 min: Option<Version>,
101 max: Option<Version>,
102 },
103}
104
105impl VersionReq {
106 pub fn parse(s: &str) -> Result<Self, String> {
107 let s = s.trim();
108
109 if s.eq_ignore_ascii_case("latest") || s == "*" {
110 return Ok(VersionReq::Latest);
111 }
112
113 if let Some(pos) = s.find(',') {
115 let left = s[..pos].trim();
116 let right = s[pos + 1..].trim();
117 let min = if left.is_empty() {
118 None
119 } else {
120 match Self::parse_single(left)? {
121 VersionReq::GreaterEq(v) => Some(v),
122 VersionReq::GreaterThan(v) => Some(v),
123 VersionReq::Exact(v) => Some(v),
124 _ => None,
125 }
126 };
127 let max = if right.is_empty() {
128 None
129 } else {
130 match Self::parse_single(right)? {
131 VersionReq::LessThan(v) => Some(v),
132 VersionReq::LessEq(v) => Some(v),
133 VersionReq::Exact(v) => Some(v),
134 _ => None,
135 }
136 };
137 return Ok(VersionReq::Range { min, max });
138 }
139
140 Self::parse_single(s)
141 }
142
143 fn parse_single(s: &str) -> Result<Self, String> {
144 let s = s.trim();
145
146 if let Some(v) = s.strip_prefix("^") {
147 Ok(VersionReq::Caret(Version::parse(v)?))
148 } else if let Some(v) = s.strip_prefix("~") {
149 Ok(VersionReq::Tilde(Version::parse(v)?))
150 } else if let Some(v) = s.strip_prefix(">=") {
151 Ok(VersionReq::GreaterEq(Version::parse(v)?))
152 } else if let Some(v) = s.strip_prefix(">") {
153 Ok(VersionReq::GreaterThan(Version::parse(v)?))
154 } else if let Some(v) = s.strip_prefix("<=") {
155 Ok(VersionReq::LessEq(Version::parse(v)?))
156 } else if let Some(v) = s.strip_prefix("<") {
157 Ok(VersionReq::LessThan(Version::parse(v)?))
158 } else {
159 Ok(VersionReq::Exact(Version::parse(s)?))
160 }
161 }
162
163 pub fn matches(&self, version: &Version) -> bool {
165 match self {
166 VersionReq::Exact(v) => version == v,
167 VersionReq::Latest => true,
168 VersionReq::Caret(v) => {
169 version >= v && version.major == v.major
171 }
172 VersionReq::Tilde(v) => {
173 version >= v && version.major == v.major && version.minor == v.minor
175 }
176 VersionReq::GreaterThan(v) => version > v,
177 VersionReq::GreaterEq(v) => version >= v,
178 VersionReq::LessThan(v) => version < v,
179 VersionReq::LessEq(v) => version <= v,
180 VersionReq::Range { min, max } => {
181 let min_ok = min.as_ref().is_none_or(|m| version >= m);
182 let max_ok = max.as_ref().is_none_or(|m| version < m);
183 min_ok && max_ok
184 }
185 }
186 }
187
188 pub fn select_best<'a>(&self, available: &'a [Version]) -> Option<&'a Version> {
190 let mut candidates: Vec<&Version> = available.iter().filter(|v| self.matches(v)).collect();
191
192 if candidates.is_empty() {
193 return None;
194 }
195
196 candidates.sort();
197 candidates.into_iter().next_back()
198 }
199}
200
201impl fmt::Display for VersionReq {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 VersionReq::Exact(v) => write!(f, "{}", v),
205 VersionReq::Latest => write!(f, "latest"),
206 VersionReq::Caret(v) => write!(f, "^{}", v),
207 VersionReq::Tilde(v) => write!(f, "~{}", v),
208 VersionReq::GreaterThan(v) => write!(f, ">{}", v),
209 VersionReq::GreaterEq(v) => write!(f, ">={}", v),
210 VersionReq::LessThan(v) => write!(f, "<{}", v),
211 VersionReq::LessEq(v) => write!(f, "<={}", v),
212 VersionReq::Range { min, max } => match (min, max) {
213 (Some(min), Some(max)) => write!(f, ">={}, <{}", min, max),
214 (Some(min), None) => write!(f, ">={}", min),
215 (None, Some(max)) => write!(f, "<{}", max),
216 (None, None) => write!(f, "*"),
217 },
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_version_parse() {
228 let v = Version::parse("1.2.3").unwrap();
229 assert_eq!(v, Version::new(1, 2, 3));
230 }
231
232 #[test]
233 fn test_version_cmp() {
234 assert!(Version::new(2, 0, 0) > Version::new(1, 9, 9));
235 assert!(Version::new(1, 10, 0) > Version::new(1, 9, 9));
236 }
237
238 #[test]
239 fn test_version_req_parse() {
240 assert_eq!(
241 VersionReq::parse("1.2.3").unwrap(),
242 VersionReq::Exact(Version::new(1, 2, 3))
243 );
244 assert_eq!(VersionReq::parse("latest").unwrap(), VersionReq::Latest);
245 assert_eq!(
246 VersionReq::parse("^1.2.3").unwrap(),
247 VersionReq::Caret(Version::new(1, 2, 3))
248 );
249 assert_eq!(
250 VersionReq::parse("~1.2.3").unwrap(),
251 VersionReq::Tilde(Version::new(1, 2, 3))
252 );
253 assert_eq!(
254 VersionReq::parse(">1.2.3").unwrap(),
255 VersionReq::GreaterThan(Version::new(1, 2, 3))
256 );
257 assert_eq!(
258 VersionReq::parse(">=1.2.3").unwrap(),
259 VersionReq::GreaterEq(Version::new(1, 2, 3))
260 );
261 assert_eq!(
262 VersionReq::parse("<1.2.3").unwrap(),
263 VersionReq::LessThan(Version::new(1, 2, 3))
264 );
265 assert_eq!(
266 VersionReq::parse("<=1.2.3").unwrap(),
267 VersionReq::LessEq(Version::new(1, 2, 3))
268 );
269 }
270
271 #[test]
272 fn test_version_req_matches() {
273 let v123 = Version::new(1, 2, 3);
274 let v124 = Version::new(1, 2, 4);
275 let v130 = Version::new(1, 3, 0);
276 let v200 = Version::new(2, 0, 0);
277
278 assert!(VersionReq::Exact(v123.clone()).matches(&v123));
279 assert!(!VersionReq::Exact(v123.clone()).matches(&v124));
280
281 assert!(VersionReq::Caret(v123.clone()).matches(&v123));
282 assert!(VersionReq::Caret(v123.clone()).matches(&v124));
283 assert!(!VersionReq::Caret(v123.clone()).matches(&v200));
284
285 assert!(VersionReq::Tilde(v123.clone()).matches(&v123));
286 assert!(VersionReq::Tilde(v123.clone()).matches(&v124));
287 assert!(!VersionReq::Tilde(v123.clone()).matches(&v130));
288
289 assert!(VersionReq::GreaterThan(v123.clone()).matches(&v124));
290 assert!(!VersionReq::GreaterThan(v123.clone()).matches(&v123));
291
292 assert!(VersionReq::GreaterEq(v123.clone()).matches(&v123));
293 assert!(VersionReq::GreaterEq(v123.clone()).matches(&v124));
294
295 assert!(VersionReq::LessThan(v123.clone()).matches(&Version::new(1, 2, 2)));
296 assert!(!VersionReq::LessThan(v123.clone()).matches(&v123));
297 }
298
299 #[test]
300 fn test_version_req_select_best() {
301 let versions = vec![
302 Version::new(1, 0, 0),
303 Version::new(1, 1, 0),
304 Version::new(1, 2, 0),
305 Version::new(1, 2, 3),
306 Version::new(2, 0, 0),
307 ];
308
309 let req = VersionReq::parse("^1.2.0").unwrap();
310 let best = req.select_best(&versions);
311 assert_eq!(best, Some(&Version::new(1, 2, 3)));
312
313 let req = VersionReq::parse("latest").unwrap();
314 let best = req.select_best(&versions);
315 assert_eq!(best, Some(&Version::new(2, 0, 0)));
316 }
317}