Skip to main content

dev_security/
producer.rs

1//! [`Producer`] adapter for [`AuditRun`].
2
3use dev_report::{CheckResult, Producer, Report, Severity};
4
5use crate::AuditRun;
6
7/// `Producer` adapter that runs an [`AuditRun`] and emits a [`Report`].
8///
9/// Subprocess failures map to a single failing
10/// [`CheckResult`] named `security::audit` with `Severity::Critical`.
11/// No panics.
12///
13/// # Example
14///
15/// ```no_run
16/// use dev_security::{AuditProducer, AuditRun, AuditScope};
17/// use dev_report::Producer;
18///
19/// let producer = AuditProducer::new(
20///     AuditRun::new("my-crate", "0.1.0").scope(AuditScope::All),
21/// );
22/// let report = producer.produce();
23/// println!("{}", report.to_json().unwrap());
24/// ```
25pub struct AuditProducer {
26    run: AuditRun,
27}
28
29impl AuditProducer {
30    /// Wrap an `AuditRun` so it can be composed with other producers.
31    pub fn new(run: AuditRun) -> Self {
32        Self { run }
33    }
34
35    /// Access the wrapped run.
36    pub fn run(&self) -> &AuditRun {
37        &self.run
38    }
39}
40
41impl Producer for AuditProducer {
42    fn produce(&self) -> Report {
43        let subject = self.run.subject().to_string();
44        let version = self.run.subject_version().to_string();
45        match self.run.execute() {
46            Ok(result) => result.into_report(),
47            Err(e) => {
48                let mut report = Report::new(&subject, &version).with_producer("dev-security");
49                let check = CheckResult::fail("security::audit", Severity::Critical)
50                    .with_detail(e.to_string())
51                    .with_tag("security")
52                    .with_tag("subprocess");
53                report.push(check);
54                report.finish();
55                report
56            }
57        }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::AuditScope;
65
66    #[test]
67    fn produce_returns_report_when_tool_missing() {
68        // The default runner image won't have cargo-audit / cargo-deny
69        // installed; the producer should surface that as a failing
70        // CheckResult rather than panicking.
71        let producer =
72            AuditProducer::new(AuditRun::new("self", "0.0.0").scope(AuditScope::Vulnerabilities));
73        let report = producer.produce();
74        assert_eq!(report.subject, "self");
75        assert!(!report.checks.is_empty());
76    }
77}