1use deps_core::normalize_operator_spacing;
4use std::cmp::Ordering;
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
14enum PrereleaseIdentifier {
15 Numeric(u64),
16 AlphaNumeric(String),
17}
18
19impl PrereleaseIdentifier {
20 fn parse(s: &str) -> Self {
21 if !s.is_empty()
22 && s.bytes().all(|b| b.is_ascii_digit())
23 && let Ok(n) = s.parse::<u64>()
24 {
25 return Self::Numeric(n);
26 }
27 Self::AlphaNumeric(s.to_string())
28 }
29}
30
31fn split_core_and_prerelease(version: &str) -> (&str, Option<&str>) {
35 let without_build = version.split('+').next().unwrap_or(version);
36 match without_build.split_once('-') {
37 Some((core, pre)) => (core, Some(pre)),
38 None => (without_build, None),
39 }
40}
41
42fn parse_core_parts(core: &str) -> Vec<u64> {
45 core.split('.')
46 .map(|s| {
47 s.chars()
48 .take_while(char::is_ascii_digit)
49 .collect::<String>()
50 .parse()
51 .unwrap_or(0)
52 })
53 .collect()
54}
55
56pub fn compare_versions(a: &str, b: &str) -> Ordering {
76 let (a_core, a_pre) = split_core_and_prerelease(a);
77 let (b_core, b_pre) = split_core_and_prerelease(b);
78
79 let a_core_parts = parse_core_parts(a_core);
80 let b_core_parts = parse_core_parts(b_core);
81
82 let max_len = a_core_parts.len().max(b_core_parts.len());
83 for i in 0..max_len {
84 let ap = a_core_parts.get(i).copied().unwrap_or(0);
85 let bp = b_core_parts.get(i).copied().unwrap_or(0);
86 match ap.cmp(&bp) {
87 Ordering::Equal => {}
88 other => return other,
89 }
90 }
91
92 match (a_pre, b_pre) {
93 (None, None) => Ordering::Equal,
94 (None, Some(_)) => Ordering::Greater,
95 (Some(_), None) => Ordering::Less,
96 (Some(a_pre), Some(b_pre)) => {
97 let a_ids: Vec<PrereleaseIdentifier> =
98 a_pre.split('.').map(PrereleaseIdentifier::parse).collect();
99 let b_ids: Vec<PrereleaseIdentifier> =
100 b_pre.split('.').map(PrereleaseIdentifier::parse).collect();
101 a_ids.cmp(&b_ids)
102 }
103 }
104}
105
106pub fn version_matches_constraint(version: &str, constraint: &str) -> bool {
110 let constraint = normalize_operator_spacing(constraint.trim());
111 version_matches_normalized_constraint(version, &constraint)
112}
113
114pub(crate) fn version_matches_normalized_constraint(version: &str, constraint: &str) -> bool {
119 if constraint.is_empty() || constraint == "any" || constraint == "*" {
120 return true;
121 }
122
123 if constraint.contains(' ') {
126 return constraint
127 .split_whitespace()
128 .all(|c| match_single_constraint(version, c));
129 }
130
131 match_single_constraint(version, constraint)
132}
133
134fn match_single_constraint(version: &str, constraint: &str) -> bool {
135 let constraint = constraint.trim();
136
137 if constraint.starts_with('^') {
138 let req_ver = constraint.trim_start_matches('^');
139 return matches_caret(version, req_ver);
140 }
141
142 if constraint.starts_with(">=") {
143 let req_ver = constraint.trim_start_matches(">=").trim();
144 return compare_versions(version, req_ver) != Ordering::Less;
145 }
146
147 if constraint.starts_with('>') {
148 let req_ver = constraint.trim_start_matches('>').trim();
149 return compare_versions(version, req_ver) == Ordering::Greater;
150 }
151
152 if constraint.starts_with("<=") {
153 let req_ver = constraint.trim_start_matches("<=").trim();
154 return compare_versions(version, req_ver) != Ordering::Greater;
155 }
156
157 if constraint.starts_with('<') {
158 let req_ver = constraint.trim_start_matches('<').trim();
159 return compare_versions(version, req_ver) == Ordering::Less;
160 }
161
162 compare_versions(version, constraint) == Ordering::Equal
164}
165
166fn matches_caret(version: &str, requirement: &str) -> bool {
167 let req_parts: Vec<u64> = requirement
171 .split('.')
172 .filter_map(|p| p.split(|c: char| !c.is_ascii_digit()).next())
173 .filter_map(|p| p.parse().ok())
174 .collect();
175 let ver_parts: Vec<u64> = version
176 .split('.')
177 .filter_map(|p| p.split(|c: char| !c.is_ascii_digit()).next())
178 .filter_map(|p| p.parse().ok())
179 .collect();
180
181 if ver_parts.is_empty() || req_parts.is_empty() {
182 return false;
183 }
184
185 if compare_versions(version, requirement) == Ordering::Less {
186 return false;
187 }
188
189 let req_major = req_parts.first().copied().unwrap_or(0);
190 let ver_major = ver_parts.first().copied().unwrap_or(0);
191
192 if req_major == 0 {
193 let req_minor = req_parts.get(1).copied().unwrap_or(0);
195 let ver_minor = ver_parts.get(1).copied().unwrap_or(0);
196 ver_major == 0 && ver_minor == req_minor
197 } else {
198 ver_major == req_major
200 }
201}
202
203pub fn is_prerelease(version: &str) -> bool {
211 version.split('+').next().unwrap_or(version).contains('-')
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_compare_versions() {
220 assert_eq!(compare_versions("1.0.0", "1.0.0"), Ordering::Equal);
221 assert_eq!(compare_versions("1.0.1", "1.0.0"), Ordering::Greater);
222 assert_eq!(compare_versions("1.0.0", "1.0.1"), Ordering::Less);
223 assert_eq!(compare_versions("2.0.0", "1.9.9"), Ordering::Greater);
224 assert_eq!(compare_versions("1.0.0", "1.0"), Ordering::Equal);
225 }
226
227 #[test]
230 fn test_compare_versions_prerelease_vs_stable() {
231 assert_eq!(compare_versions("2.0.0", "2.0.0-beta1"), Ordering::Greater);
232 assert_eq!(compare_versions("2.0.0-beta1", "2.0.0"), Ordering::Less);
233 assert_ne!(compare_versions("2.0.0", "2.0.0-beta1"), Ordering::Equal);
234 assert_ne!(
235 compare_versions("2.10.0-nullsafety.1", "2.10.0"),
236 Ordering::Equal
237 );
238 }
239
240 #[test]
241 fn test_compare_versions_prerelease_identifier_ordering() {
242 assert_eq!(compare_versions("1.0.0-1", "1.0.0-alpha"), Ordering::Less);
244 assert_eq!(
246 compare_versions("1.0.0-alpha", "1.0.0-beta"),
247 Ordering::Less
248 );
249 assert_eq!(
251 compare_versions("1.0.0-alpha.2", "1.0.0-alpha.10"),
252 Ordering::Less
253 );
254 assert_eq!(
256 compare_versions("1.0.0-alpha.1", "1.0.0-alpha"),
257 Ordering::Greater
258 );
259 }
260
261 #[test]
262 fn test_compare_versions_build_metadata_ignored() {
263 assert_eq!(
264 compare_versions("1.0.0+build1", "1.0.0+build2"),
265 Ordering::Equal
266 );
267 assert_eq!(
268 compare_versions("1.0.0-beta+build1", "1.0.0-beta+build2"),
269 Ordering::Equal
270 );
271 }
272
273 #[test]
276 fn test_compare_versions_sorts_prerelease_below_stable() {
277 let mut versions = vec!["2.0.0-beta1", "2.0.0", "2.0.0-alpha"];
278 versions.sort_by(|a, b| compare_versions(a, b));
279 assert_eq!(versions, vec!["2.0.0-alpha", "2.0.0-beta1", "2.0.0"]);
280 }
281
282 #[test]
283 fn test_is_prerelease() {
284 assert!(!is_prerelease("1.0.0"));
285 assert!(!is_prerelease("1.0.0+build.1"));
286 assert!(is_prerelease("1.0.0-dev.1"));
287 assert!(is_prerelease("2.10.0-nullsafety.1"));
290 assert!(is_prerelease("1.0.0-nullsafety.1+build"));
291 }
292
293 #[test]
294 fn test_caret_constraint() {
295 assert!(version_matches_constraint("1.0.0", "^1.0.0"));
296 assert!(version_matches_constraint("1.5.0", "^1.0.0"));
297 assert!(version_matches_constraint("1.99.99", "^1.0.0"));
298 assert!(!version_matches_constraint("2.0.0", "^1.0.0"));
299 assert!(!version_matches_constraint("0.9.0", "^1.0.0"));
300 }
301
302 #[test]
307 fn test_caret_constraint_excludes_own_floor_prerelease() {
308 assert!(!version_matches_constraint("1.0.0-beta", "^1.0.0"));
309 }
310
311 #[test]
312 fn test_caret_constraint_zero_major() {
313 assert!(version_matches_constraint("0.1.0", "^0.1.0"));
315 assert!(version_matches_constraint("0.1.5", "^0.1.0"));
316 assert!(!version_matches_constraint("0.2.0", "^0.1.0"));
317 assert!(!version_matches_constraint("0.99.0", "^0.1.0"));
318 assert!(!version_matches_constraint("1.0.0", "^0.1.0"));
319 }
320
321 #[test]
322 fn test_range_constraint() {
323 assert!(version_matches_constraint("1.5.0", ">=1.0.0 <2.0.0"));
324 assert!(version_matches_constraint("1.0.0", ">=1.0.0 <2.0.0"));
325 assert!(!version_matches_constraint("2.0.0", ">=1.0.0 <2.0.0"));
326 assert!(!version_matches_constraint("0.9.0", ">=1.0.0 <2.0.0"));
327 }
328
329 #[test]
330 fn test_range_constraint_spaced_operators() {
331 assert!(version_matches_constraint("1.15.0", ">= 1.15.0 < 2.0.0"));
332 assert!(version_matches_constraint("1.99.0", ">= 1.15.0 < 2.0.0"));
333 assert!(!version_matches_constraint("1.14.0", ">= 1.15.0 < 2.0.0"));
334 assert!(!version_matches_constraint("2.0.0", ">= 1.15.0 < 2.0.0"));
335 }
336
337 #[test]
338 fn test_caret_combined_with_spaced_upper_bound() {
339 assert!(version_matches_constraint("1.5.0", "^1.0.0 < 2.0.0"));
342 assert!(version_matches_constraint("1.99.0", "^1.0.0 < 2.0.0"));
343 assert!(!version_matches_constraint("2.0.0", "^1.0.0 < 2.0.0"));
344 assert!(!version_matches_constraint("0.9.0", "^1.0.0 < 2.0.0"));
345 }
346
347 #[test]
348 fn test_exact_constraint() {
349 assert!(version_matches_constraint("1.0.0", "1.0.0"));
350 assert!(!version_matches_constraint("1.0.1", "1.0.0"));
351 }
352
353 #[test]
354 fn test_any_constraint() {
355 assert!(version_matches_constraint("1.0.0", "any"));
356 assert!(version_matches_constraint("99.0.0", "any"));
357 assert!(version_matches_constraint("1.0.0", ""));
358 }
359
360 #[test]
361 fn test_comparison_operators() {
362 assert!(version_matches_constraint("1.5.0", ">=1.0.0"));
363 assert!(version_matches_constraint("1.0.0", ">=1.0.0"));
364 assert!(!version_matches_constraint("0.9.0", ">=1.0.0"));
365
366 assert!(version_matches_constraint("2.0.0", ">1.0.0"));
367 assert!(!version_matches_constraint("1.0.0", ">1.0.0"));
368
369 assert!(version_matches_constraint("1.0.0", "<=1.0.0"));
370 assert!(!version_matches_constraint("1.1.0", "<=1.0.0"));
371
372 assert!(version_matches_constraint("0.9.0", "<1.0.0"));
373 assert!(!version_matches_constraint("1.0.0", "<1.0.0"));
374 }
375}