hakari/
helpers.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use guppy::Version;
5use std::fmt;
6
7/// A formatting wrapper that may print out a minimum version that would match the provided version.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub(crate) struct VersionDisplay<'a> {
10    version: &'a Version,
11    exact_versions: bool,
12    // Adding build metadata is incorrect (Cargo emits a warning when build metadata is present in a
13    // dependency specification), but support this for compatibility with older versions of hakari.
14    with_build_metadata: bool,
15}
16
17impl<'a> VersionDisplay<'a> {
18    pub(crate) fn new(
19        version: &'a Version,
20        exact_versions: bool,
21        with_build_metadata: bool,
22    ) -> Self {
23        Self {
24            version,
25            exact_versions,
26            with_build_metadata,
27        }
28    }
29}
30
31impl fmt::Display for VersionDisplay<'_> {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        if !self.exact_versions && self.version.pre.is_empty() {
34            // Minimal versions permitted, so attempt to minimize the version.
35            if self.version.major >= 1 {
36                return write!(f, "{}", self.version.major);
37            }
38            if self.version.minor >= 1 {
39                return write!(f, "{}.{}", self.version.major, self.version.minor);
40            }
41        }
42        write!(
43            f,
44            "{}.{}.{}",
45            self.version.major, self.version.minor, self.version.patch
46        )?;
47        if !self.version.pre.is_empty() {
48            write!(f, "-{}", self.version.pre)?;
49        }
50        if self.with_build_metadata && !self.version.build.is_empty() {
51            write!(f, "+{}", self.version.build)?;
52        }
53        Ok(())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use fixtures::json::*;
61    use guppy::{graph::DependencyDirection, VersionReq};
62
63    #[test]
64    fn min_version() {
65        let versions = vec![
66            ("1.4.0", "1", "1.4.0", "1", "1.4.0"),
67            ("2.8.0", "2", "2.8.0", "2", "2.8.0"),
68            ("0.4.2", "0.4", "0.4.2", "0.4", "0.4.2"),
69            ("0.0.7", "0.0.7", "0.0.7", "0.0.7", "0.0.7"),
70            ("1.4.0-b1", "1.4.0-b1", "1.4.0-b1", "1.4.0-b1", "1.4.0-b1"),
71            (
72                "2.8.0-a.1+v123",
73                "2.8.0-a.1",
74                "2.8.0-a.1",
75                "2.8.0-a.1+v123",
76                "2.8.0-a.1+v123",
77            ),
78            ("4.2.3+g456", "4", "4.2.3", "4", "4.2.3+g456"),
79        ];
80
81        for (version_str, min, exact, min_with_build_metadata, exact_with_build_metadata) in
82            versions
83        {
84            let version = Version::parse(version_str).expect("valid version");
85            let version_req = VersionReq::parse(min).expect("valid version req");
86            assert!(
87                version_req.matches(&version),
88                "version req {} should match version {}",
89                min,
90                version
91            );
92            assert_eq!(
93                &format!("{}", VersionDisplay::new(&version, false, false)),
94                min
95            );
96            assert_eq!(
97                &format!("{}", VersionDisplay::new(&version, true, false)),
98                exact
99            );
100            assert_eq!(
101                &format!("{}", VersionDisplay::new(&version, false, true)),
102                min_with_build_metadata
103            );
104            assert_eq!(
105                &format!("{}", VersionDisplay::new(&version, true, true)),
106                exact_with_build_metadata
107            );
108        }
109    }
110
111    #[test]
112    fn min_versions_match() {
113        for (&name, fixture) in JsonFixture::all_fixtures() {
114            let graph = fixture.graph();
115            for package in graph.resolve_all().packages(DependencyDirection::Forward) {
116                let version = package.version();
117                let min_version = format!("{}", VersionDisplay::new(version, false, false));
118                let version_req = VersionReq::parse(&min_version).expect("valid version req");
119
120                assert!(
121                    version_req.matches(version),
122                    "for fixture '{}', for package '{}', min version req {} should match version {}",
123                    name,
124                    package.id(),
125                    min_version,
126                    version,
127                );
128
129                let min_version_with_build_metadata =
130                    format!("{}", VersionDisplay::new(version, false, true));
131                let version_req =
132                    VersionReq::parse(&min_version_with_build_metadata).expect("valid version req");
133
134                assert!(
135                    version_req.matches(version),
136                    "for fixture '{}', for package '{}', min version (with build metadata) req {} should match version {}",
137                    name,
138                    package.id(),
139                    min_version,
140                    version,
141                );
142            }
143        }
144    }
145}