typescript_tools/
unpinned_dependencies.rs

1use std::{fmt::Display, path::PathBuf};
2
3use crate::types::PackageName;
4
5#[derive(Clone, Debug, Eq, PartialEq, Hash)]
6#[non_exhaustive]
7pub(crate) struct UnpinnedDependency {
8    pub name: PackageName,
9    pub actual: String,
10    pub expected: String,
11}
12
13impl Display for UnpinnedDependency {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        write!(
16            f,
17            "dependency: {}\texpected: {}\tgot: {}",
18            self.name, self.expected, self.actual
19        )
20    }
21}
22
23#[derive(Clone, Debug)]
24pub(crate) struct UnpinnedPackageDependencies(PathBuf, Vec<UnpinnedDependency>);
25
26impl Display for UnpinnedPackageDependencies {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        writeln!(
29            f,
30            "File contains unpinned dependency versions: {:?}",
31            self.0
32        )?;
33        for unpinned_dependency in self.1.iter() {
34            writeln!(f, "\t{}", unpinned_dependency)?;
35        }
36        Ok(())
37    }
38}
39
40impl From<(PathBuf, Vec<UnpinnedDependency>)> for UnpinnedPackageDependencies {
41    fn from(value: (PathBuf, Vec<UnpinnedDependency>)) -> Self {
42        Self(value.0, value.1)
43    }
44}
45
46#[derive(Clone, Debug)]
47pub struct UnpinnedMonorepoDependencies(Vec<UnpinnedPackageDependencies>);
48
49impl Display for UnpinnedMonorepoDependencies {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        for unpinned_package_dependencies in self.0.iter() {
52            write!(f, "{}", unpinned_package_dependencies)?;
53        }
54        Ok(())
55    }
56}
57
58impl From<Vec<UnpinnedPackageDependencies>> for UnpinnedMonorepoDependencies {
59    fn from(value: Vec<UnpinnedPackageDependencies>) -> Self {
60        Self(value)
61    }
62}
63
64impl FromIterator<(PathBuf, Vec<UnpinnedDependency>)> for UnpinnedMonorepoDependencies {
65    fn from_iter<T: IntoIterator<Item = (PathBuf, Vec<UnpinnedDependency>)>>(iter: T) -> Self {
66        let collection = iter
67            .into_iter()
68            .filter(|(_package_name, unpinned_dependencies)| !unpinned_dependencies.is_empty())
69            .map(Into::into)
70            .collect();
71        Self(collection)
72    }
73}
74
75impl UnpinnedMonorepoDependencies {
76    pub fn is_empty(&self) -> bool {
77        self.0.is_empty()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::path::PathBuf;
84
85    use super::{UnpinnedDependency, UnpinnedMonorepoDependencies, UnpinnedPackageDependencies};
86
87    #[test]
88    fn should_display_all_data() {
89        let unpinned: UnpinnedMonorepoDependencies = vec![UnpinnedPackageDependencies(
90            PathBuf::from("packages/a/package.json"),
91            vec![
92                UnpinnedDependency {
93                    name: "one".into(),
94                    actual: "0.0.0".into(),
95                    expected: "2.0.0".into(),
96                },
97                UnpinnedDependency {
98                    name: "two".into(),
99                    actual: "0.0.0".into(),
100                    expected: "2.0.0".into(),
101                },
102            ],
103        )]
104        .into();
105
106        let expected = r#"
107File contains unpinned dependency versions: "packages/a/package.json"
108	dependency: one	expected: 2.0.0	got: 0.0.0
109	dependency: two	expected: 2.0.0	got: 0.0.0
110"#
111        .trim_start();
112        let actual = format!("{}", unpinned);
113        assert_eq!(expected, actual);
114    }
115}