use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static ALLOWED_LICENSE_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\s+\S+ v\d+\.\d+[\.\d]* \([A-Za-z0-9\-\. ]+\)\s*$").unwrap());
static PROGRESS_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^(?:Gathered|Checked|Fetched)[^\n]*\n?").unwrap());
pub fn compress_deny(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = PROGRESS_RE.replace_all(&cleaned, "");
let s = ALLOWED_LICENSE_RE.replace_all(&s, "");
let mut violations: Vec<&str> = Vec::new();
let mut summary_lines: Vec<&str> = Vec::new();
let mut advisory_lines: Vec<&str> = Vec::new();
for line in s.lines() {
let t = line.trim();
if t.is_empty() {
continue;
}
if t.starts_with("error[")
|| t.starts_with("warning[")
|| t.starts_with(" = ")
|| t.starts_with("note:")
|| t.contains("RUSTSEC-")
|| t.contains("advisory")
|| t.contains("license")
&& (t.contains("denied") || t.contains("not explicitly allowed"))
{
violations.push(line);
} else if t.contains("advisories") || t.contains("licenses") || t.contains("bans") {
summary_lines.push(line);
} else if t.starts_with("RUSTSEC") || t.contains("RUSTSEC-") {
advisory_lines.push(line);
}
}
if violations.is_empty() && summary_lines.is_empty() && advisory_lines.is_empty() {
let pass_lines: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("advisories ok")
|| t.contains("licenses ok")
|| t.contains("bans ok")
|| t.contains("sources ok")
|| t.contains("0 errors")
|| t.starts_with("Finished"))
})
.collect();
if pass_lines.is_empty() {
return "cargo-deny: all checks passed".to_string();
}
return pass_lines.join("\n");
}
let mut out = Vec::new();
out.extend(violations);
out.extend(advisory_lines);
out.extend(summary_lines);
out.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_allowed_crate_lines() {
let raw = " serde v1.0.195 (MIT OR Apache-2.0)\n tokio v1.35.1 (MIT)\nerror[A001]: crate 'old-crate' is banned\n = use `new-crate` instead\n";
let out = compress_deny(raw);
assert!(!out.contains("serde v1"), "{out}");
assert!(!out.contains("tokio v1"), "{out}");
assert!(out.contains("old-crate"), "{out}");
assert!(out.contains("banned"), "{out}");
}
#[test]
fn keeps_rustsec_advisories() {
let raw = "error[A001]: Unmaintained advisory for `ansi_term`\n = RUSTSEC-2021-0139: ansi_term is Unmaintained\n = Advisory: https://rustsec.org/advisories/RUSTSEC-2021-0139\n";
let out = compress_deny(raw);
assert!(out.contains("RUSTSEC-2021-0139"), "{out}");
assert!(out.contains("Unmaintained"), "{out}");
}
#[test]
fn all_pass_returns_compact_message() {
let raw = "Gathered 142 crates\n tokio v1.35.1 (MIT)\n serde v1.0.195 (MIT OR Apache-2.0)\nadvisories ok\nlicenses ok\nbans ok\nsources ok\n";
let out = compress_deny(raw);
assert!(!out.contains("tokio v1"), "{out}");
assert!(out.contains("ok") || out.contains("passed"), "{out}");
}
#[test]
fn keeps_license_violations() {
let raw = "error[L001]: crate 'gpl-crate' is licensed under GPL-3.0 which is not allowed\n note: Use an MIT-licensed alternative\n";
let out = compress_deny(raw);
assert!(out.contains("gpl-crate"), "{out}");
assert!(out.contains("GPL-3.0"), "{out}");
}
}