1use semver::{Version, VersionReq};
2use std::collections::HashSet;
3use std::error::Error;
4use std::fmt::{self, Display};
5
6#[derive(Debug)]
7pub struct NoValidVersion;
8
9impl Display for NoValidVersion {
10 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11 write!(f, "No Valid Version")
12 }
13}
14
15impl Error for NoValidVersion {}
16
17pub fn what_version(
56 version_requirements: HashSet<VersionReq>,
57 versions: Vec<Version>,
58) -> Result<Version, NoValidVersion> {
59 versions
60 .iter()
61 .filter(|ver| version_requirements.iter().all(|req| req.matches(ver)))
62 .max_by(|arg0: &&semver::Version, other: &&semver::Version| {
63 Version::cmp_precedence(arg0, other)
64 })
65 .cloned()
66 .ok_or(NoValidVersion)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 const VERSIONS: [&str; 14] = [
74 "2.5.1", "2.5.0", "2.4.0", "2.3.0", "2.2.0", "2.1.1", "2.1.0", "2.0.2", "2.0.1", "2.0.0",
75 "1.1.4", "1.1.2", "1.1.1", "1.0.3",
76 ];
77
78 #[yare::parameterized(
79 all_same = { vec!["2.5.1", "2.5.1", "2.5.1"], Some("2.5.1") },
80 all_caret = { vec!["^2", "^2", "^2"], Some("2.5.1") },
81 all_wildcard = { vec!["*", "*", "*"], Some("2.5.1") },
82 one_tilde = { vec!["~1.1.1", "^1", "1"], Some("1.1.4") },
83 one_wildcard = { vec!["1.*", "^1", "1.1.0"], Some("1.1.4") },
84 one_equals = { vec!["=2.2.0", "^2", "2.1.0"], Some("2.2.0") },
85 upper_bound = { vec!["^2.0", "<2.5"], Some("2.4.0") },
86 all_ranges = { vec!["^2.0", ">=2.1", "<2.3"], Some("2.2.0") },
87 lower_bound = { vec!["^1.0", "<1.1"], Some("1.0.3") },
88 no_requirements = { vec![], Some("2.5.1") }
89 )]
90 fn happy(version_reqs: Vec<&str>, expected: Option<&str>) {
91 let version_requirements: HashSet<VersionReq> = version_reqs
92 .into_iter()
93 .map(|req| req.parse().unwrap())
94 .collect();
95 let versions: Vec<Version> = VERSIONS
96 .into_iter()
97 .map(|ver| ver.parse().unwrap())
98 .collect();
99 let expected = expected.map(|ver| ver.parse::<Version>().unwrap());
100
101 let actual = what_version(version_requirements, versions);
102
103 assert_eq!(actual.ok(), expected);
104 }
105
106 #[yare::parameterized(
107 no_match = { vec!["^3"], None },
108 conflicting_ranges = { vec!["^2.0", "<2.0"], None },
109 invalid_version = { vec!["=3.0.0", "^2"], None },
110 )]
111 fn sad(version_reqs: Vec<&str>, expected: Option<&str>) {
112 let version_requirements: HashSet<VersionReq> = version_reqs
113 .into_iter()
114 .map(|req| req.parse().unwrap())
115 .collect();
116 let versions: Vec<Version> = VERSIONS
117 .into_iter()
118 .map(|ver| ver.parse().unwrap())
119 .collect();
120 let expected = expected.map(|ver| ver.parse::<Version>().unwrap());
121
122 let actual = what_version(version_requirements, versions);
123
124 assert_eq!(actual.ok(), expected);
125 }
126}