cargo_workspace_lints/
lib.rs1use cargo_metadata::{MetadataCommand, PackageId};
4use std::{collections::HashSet, fs};
5use std::{error, fmt, io};
6
7pub fn validate_workspace(
18 metadata_command: &MetadataCommand,
19 verbose: bool,
20) -> Result<(), WorkspaceValidationError> {
21 let metadata = metadata_command.exec()?;
22 let workspace_members = metadata
23 .workspace_members
24 .into_iter()
25 .collect::<HashSet<_>>();
26 let mut failing_packages = Vec::new();
27 for package in metadata.packages {
28 if !workspace_members.contains(&package.id) {
30 continue;
31 }
32 let manifest_path = package.manifest_path.as_path();
33 let manifest: toml::Table = toml::from_str(&fs::read_to_string(manifest_path)?)?;
34 if let Err(kind) = validate_package(&package, &manifest, verbose) {
35 failing_packages.push(PackageValidationError {
36 kind,
37 package: package.id,
38 });
39 }
40 }
41 if failing_packages.is_empty() {
42 Ok(())
43 } else {
44 Err(WorkspaceValidationError::FailingPackages(failing_packages))
45 }
46}
47
48pub fn validate_package(
58 package: &cargo_metadata::Package,
59 manifest: &toml::Table,
60 verbose: bool,
61) -> Result<(), PackageValidationErrorKind> {
62 match manifest
63 .get("lints")
64 .and_then(|lints| lints.get("workspace"))
65 {
66 Some(toml::Value::Boolean(true)) => {
67 if verbose {
68 eprintln!(
69 "PASS: Package {} ({})",
70 package.name,
71 package.manifest_path.as_str()
72 );
73 }
74 Ok(())
75 }
76 Some(other_value) => {
77 if verbose {
78 eprintln!(
79 "FAIL: Package {} ({}) has `lints.workspace = {other_value}`",
80 package.name,
81 package.manifest_path.as_str()
82 );
83 }
84 Err(PackageValidationErrorKind::WorkspaceLintsWrongValue(
85 other_value.clone(),
86 ))
87 }
88 None => {
89 if verbose {
90 eprintln!(
91 "FAIL: Package {} ({}) missing `lints.workspace` field",
92 package.name,
93 package.manifest_path.as_str()
94 );
95 }
96 Err(PackageValidationErrorKind::WorkspaceLintsMissing)
97 }
98 }
99}
100
101#[derive(Debug)]
103pub enum WorkspaceValidationError {
104 Io(io::Error),
106 CargoMetadata(cargo_metadata::Error),
108 Toml(toml::de::Error),
110 FailingPackages(Vec<PackageValidationError>),
112}
113impl From<io::Error> for WorkspaceValidationError {
114 fn from(error: io::Error) -> Self {
115 Self::Io(error)
116 }
117}
118impl From<cargo_metadata::Error> for WorkspaceValidationError {
119 fn from(error: cargo_metadata::Error) -> Self {
120 Self::CargoMetadata(error)
121 }
122}
123impl From<toml::de::Error> for WorkspaceValidationError {
124 fn from(error: toml::de::Error) -> Self {
125 Self::Toml(error)
126 }
127}
128impl fmt::Display for WorkspaceValidationError {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::Io(e) => f.write_fmt(format_args!(
132 "Disk I/O Error reading `Cargo.toml` files:\n {e}\n"
133 )),
134 Self::CargoMetadata(e) => f.write_fmt(format_args!(
135 "Error reading Cargo manifest data:\n {e}\n"
136 )),
137 Self::Toml(e) => f.write_fmt(format_args!(
138 "Error parsing `Cargo.toml` files as TOML:\n {e}\n"
139 )),
140 Self::FailingPackages(package_failures) => {
141 f.write_str("Failing packages:")?;
142 for failure in package_failures {
143 f.write_fmt(format_args!("\n* {failure}"))?;
144 }
145 Ok(())
146 }
147 }
148 }
149}
150
151#[derive(Debug)]
153pub struct PackageValidationError {
154 kind: PackageValidationErrorKind,
156 package: PackageId,
158}
159
160impl fmt::Display for PackageValidationError {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.write_fmt(format_args!(
163 "Package {}:\n {}\n",
164 self.package, self.kind
165 ))
166 }
167}
168impl error::Error for PackageValidationError {}
169
170#[derive(Debug)]
172pub enum PackageValidationErrorKind {
173 WorkspaceLintsMissing,
175 WorkspaceLintsWrongValue(toml::Value),
177}
178impl fmt::Display for PackageValidationErrorKind {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Self::WorkspaceLintsMissing => f.write_str("No `workspace.lints` field found"),
182 Self::WorkspaceLintsWrongValue(found) => {
183 f.write_fmt(format_args!("workspace.lints = {found}, expected `true`"))
184 }
185 }
186 }
187}