what_version_core/
lib.rs

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
17/// Determines the highest version from a list of versions that satisfies all given version requirements.
18///
19/// ## Arguments
20///
21/// * `version_requirements` - A `HashSet` of `VersionReq` specifying the version constraints.
22/// * `versions` - A `Vec` of `Version` representing the available versions to choose from.
23///
24/// ## Returns
25///
26/// * `Ok(Version)` - The highest version that satisfies all the given requirements.
27/// * `Err(())` - If no version satisfies all the given requirements.
28///
29/// ## Examples
30///
31/// ```rust
32/// use std::collections::HashSet;
33/// use semver::{Version, VersionReq};
34/// use what_version_core::what_version;
35///
36/// let version_requirements: HashSet<VersionReq> = vec![
37///     "^2.0".parse().unwrap(),
38///     ">=2.1".parse().unwrap(),
39///     "<2.3".parse().unwrap(),
40/// ]
41/// .into_iter()
42/// .collect();
43///
44/// let versions: Vec<Version> = vec![
45///     "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",
46/// ]
47/// .into_iter()
48/// .map(|ver| ver.parse().unwrap())
49/// .collect();
50///
51/// let result = what_version(version_requirements, versions);
52///
53/// assert_eq!(result.unwrap(), Version::parse("2.2.0").unwrap());
54/// ```
55pub 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}