what_version_core/
lib.rs

1use std::collections::HashSet;
2
3use semver::{Version, VersionReq};
4
5/// Determines the highest version from a list of versions that satisfies all given version requirements.
6///
7/// ## Arguments
8///
9/// * `version_requirements` - A `HashSet` of `VersionReq` specifying the version constraints.
10/// * `versions` - A `Vec` of `Version` representing the available versions to choose from.
11///
12/// ## Returns
13///
14/// * `Ok(Version)` - The highest version that satisfies all the given requirements.
15/// * `Err(())` - If no version satisfies all the given requirements.
16///
17/// ## Examples
18///
19/// ```rust
20/// use std::collections::HashSet;
21/// use semver::{Version, VersionReq};
22/// use what_version_core::what_version;
23///
24/// let version_requirements: HashSet<VersionReq> = vec![
25///     "^2.0".parse().unwrap(),
26///     ">=2.1".parse().unwrap(),
27///     "<2.3".parse().unwrap(),
28/// ]
29/// .into_iter()
30/// .collect();
31///
32/// let versions: Vec<Version> = vec![
33///     "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",
34/// ]
35/// .into_iter()
36/// .map(|ver| ver.parse().unwrap())
37/// .collect();
38///
39/// let result = what_version(version_requirements, versions);
40///
41/// assert_eq!(result.unwrap(), Version::parse("2.2.0").unwrap());
42/// ```
43pub fn what_version(
44    version_requirements: HashSet<VersionReq>,
45    versions: Vec<Version>,
46) -> Result<Version, ()> {
47    versions
48        .iter()
49        .filter(|ver| version_requirements.iter().all(|req| req.matches(ver)))
50        .max_by(|arg0: &&semver::Version, other: &&semver::Version| {
51            Version::cmp_precedence(*arg0, *other)
52        })
53        .cloned()
54        .ok_or(())
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    const VERSIONS: [&str; 14] = [
62        "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",
63        "1.1.4", "1.1.2", "1.1.1", "1.0.3",
64    ];
65
66    #[yare::parameterized(
67        all_same = { vec!["2.5.1", "2.5.1", "2.5.1"], Some("2.5.1") },
68        all_caret = { vec!["^2", "^2", "^2"], Some("2.5.1") },
69        all_wildcard = { vec!["*", "*", "*"], Some("2.5.1") },
70        one_tilde = { vec!["~1.1.1", "^1", "1"], Some("1.1.4") },
71        one_wildcard = { vec!["1.*", "^1", "1.1.0"], Some("1.1.4") },
72        one_equals = { vec!["=2.2.0", "^2", "2.1.0"], Some("2.2.0") },
73        upper_bound = { vec!["^2.0", "<2.5"], Some("2.4.0") },
74        all_ranges = { vec!["^2.0", ">=2.1", "<2.3"], Some("2.2.0") },
75        lower_bound = { vec!["^1.0", "<1.1"], Some("1.0.3") },
76        no_requirements = { vec![], Some("2.5.1") }
77    )]
78    fn happy(version_reqs: Vec<&str>, expected: Option<&str>) {
79        let version_requirements: HashSet<VersionReq> = version_reqs
80            .into_iter()
81            .map(|req| req.parse().unwrap())
82            .collect();
83        let versions: Vec<Version> = VERSIONS
84            .into_iter()
85            .map(|ver| ver.parse().unwrap())
86            .collect();
87        let expected = expected.map(|ver| ver.parse::<Version>().unwrap());
88
89        let actual = what_version(version_requirements, versions);
90
91        assert_eq!(actual.ok(), expected);
92    }
93
94    #[yare::parameterized(
95        no_match = { vec!["^3"], None },
96        conflicting_ranges = { vec!["^2.0", "<2.0"], None },
97        invalid_version = { vec!["=3.0.0", "^2"], None },
98    )]
99    fn sad(version_reqs: Vec<&str>, expected: Option<&str>) {
100        let version_requirements: HashSet<VersionReq> = version_reqs
101            .into_iter()
102            .map(|req| req.parse().unwrap())
103            .collect();
104        let versions: Vec<Version> = VERSIONS
105            .into_iter()
106            .map(|ver| ver.parse().unwrap())
107            .collect();
108        let expected = expected.map(|ver| ver.parse::<Version>().unwrap());
109
110        let actual = what_version(version_requirements, versions);
111
112        assert_eq!(actual.ok(), expected);
113    }
114}