Skip to main content

changeset_operations/traits/
inherited_version_checker.rs

1use std::path::Path;
2
3use changeset_core::PackageInfo;
4
5use crate::Result;
6
7/// Checks whether packages use inherited workspace versions.
8pub trait InheritedVersionChecker: Send + Sync {
9    /// # Errors
10    ///
11    /// Returns an error if the manifest cannot be read.
12    fn has_inherited_version(&self, manifest_path: &Path) -> Result<bool>;
13
14    /// # Errors
15    ///
16    /// Returns an error if any manifest cannot be read.
17    fn find_packages_with_inherited_versions(
18        &self,
19        packages: &[PackageInfo],
20    ) -> Result<Vec<String>> {
21        let mut inherited = Vec::new();
22        for pkg in packages {
23            let manifest_path = pkg.path.join("Cargo.toml");
24            if self.has_inherited_version(&manifest_path)? {
25                inherited.push(pkg.name.clone());
26            }
27        }
28        Ok(inherited)
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use std::path::PathBuf;
36
37    struct TestChecker {
38        inherited: std::collections::HashSet<PathBuf>,
39        fail_on: Option<PathBuf>,
40    }
41
42    impl TestChecker {
43        fn new() -> Self {
44            Self {
45                inherited: std::collections::HashSet::new(),
46                fail_on: None,
47            }
48        }
49
50        fn with_inherited(mut self, path: PathBuf) -> Self {
51            self.inherited.insert(path);
52            self
53        }
54
55        fn failing_on(mut self, path: PathBuf) -> Self {
56            self.fail_on = Some(path);
57            self
58        }
59    }
60
61    impl InheritedVersionChecker for TestChecker {
62        fn has_inherited_version(&self, manifest_path: &Path) -> Result<bool> {
63            if let Some(ref fail_path) = self.fail_on {
64                if manifest_path == fail_path {
65                    return Err(crate::OperationError::Io(std::io::Error::new(
66                        std::io::ErrorKind::PermissionDenied,
67                        "mock failure",
68                    )));
69                }
70            }
71            Ok(self.inherited.contains(manifest_path))
72        }
73    }
74
75    fn make_package(name: &str, path: &str) -> PackageInfo {
76        PackageInfo {
77            name: name.to_string(),
78            version: "1.0.0".parse().expect("valid version"),
79            path: PathBuf::from(path),
80        }
81    }
82
83    #[test]
84    fn find_packages_returns_empty_for_no_inherited() {
85        let checker = TestChecker::new();
86        let packages = vec![make_package("crate-a", "/pkg/a")];
87
88        let result = checker
89            .find_packages_with_inherited_versions(&packages)
90            .expect("should succeed");
91
92        assert!(result.is_empty());
93    }
94
95    #[test]
96    fn find_packages_returns_inherited_package_names() {
97        let checker = TestChecker::new()
98            .with_inherited(PathBuf::from("/pkg/a/Cargo.toml"))
99            .with_inherited(PathBuf::from("/pkg/c/Cargo.toml"));
100
101        let packages = vec![
102            make_package("crate-a", "/pkg/a"),
103            make_package("crate-b", "/pkg/b"),
104            make_package("crate-c", "/pkg/c"),
105        ];
106
107        let result = checker
108            .find_packages_with_inherited_versions(&packages)
109            .expect("should succeed");
110
111        assert_eq!(result, vec!["crate-a", "crate-c"]);
112    }
113
114    #[test]
115    fn find_packages_propagates_errors() {
116        let checker = TestChecker::new().failing_on(PathBuf::from("/pkg/b/Cargo.toml"));
117
118        let packages = vec![
119            make_package("crate-a", "/pkg/a"),
120            make_package("crate-b", "/pkg/b"),
121        ];
122
123        let result = checker.find_packages_with_inherited_versions(&packages);
124
125        assert!(result.is_err());
126    }
127}