Skip to main content

ruso_runtime/runtime/
report.rs

1use crate::contract::Severity;
2use crate::runtime::spec::CheckMetadata;
3
4/// A single positive result: the check's metadata resolved into a concrete
5/// finding, plus the evidence captured during the run. Built from
6/// [`CheckMetadata`] via [`Finding::from_metadata`].
7#[derive(Debug, Clone)]
8pub struct Finding {
9    /// Finding title (from metadata `name`).
10    pub name: String,
11    /// What the check found.
12    pub description: Option<String>,
13    /// The risk this finding represents.
14    pub impact: Option<String>,
15    /// Severity (defaults to `Info` when the check set none).
16    pub severity: Severity,
17    /// Check author.
18    pub author: Option<String>,
19    /// Associated CVE identifiers.
20    pub cve: Vec<String>,
21    /// Associated CWE identifiers.
22    pub cwe: Vec<String>,
23    /// Reference URLs.
24    pub references: Vec<String>,
25    /// CVSS vector strings.
26    pub cvss: Vec<String>,
27    /// CVSS numeric scores.
28    pub cvss_score: Vec<String>,
29    /// Remediation guidance.
30    pub mitigation: Option<String>,
31    /// Proof strings captured by `evidence` rules.
32    pub evidence: Vec<String>,
33}
34
35impl Finding {
36    /// Build the single finding for a script from its metadata block.
37    pub fn from_metadata(metadata: &CheckMetadata, evidence: Vec<String>) -> Option<Self> {
38        let name = metadata.name.clone()?;
39        Some(Self {
40            name,
41            description: metadata.description.clone(),
42            impact: metadata.impact.clone(),
43            severity: metadata.severity.clone().unwrap_or(Severity::Info),
44            author: metadata.author.clone(),
45            cve: metadata.cve.clone(),
46            cwe: metadata.cwe.clone(),
47            references: metadata.references.clone(),
48            cvss: metadata.cvss.clone(),
49            cvss_score: metadata.cvss_score.clone(),
50            mitigation: metadata.mitigation.clone(),
51            evidence,
52        })
53    }
54}
55
56/// A run's findings. At most one finding is produced per script execution, so
57/// this holds zero or one entry.
58#[derive(Debug, Clone, Default)]
59pub struct Report {
60    /// The findings emitted (zero or one).
61    pub findings: Vec<Finding>,
62}
63
64impl Report {
65    /// Replace any prior finding — at most one per script execution.
66    pub fn set_finding(&mut self, finding: Finding) {
67        self.findings.clear();
68        self.findings.push(finding);
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use crate::contract::Severity;
75    use crate::runtime::spec::CheckMetadata;
76
77    use super::Finding;
78
79    #[test]
80    fn from_metadata_requires_name() {
81        let meta = CheckMetadata {
82            name: Some("Check".into()),
83            description: Some("desc".into()),
84            severity: Some(Severity::Medium),
85            ..Default::default()
86        };
87        let finding = Finding::from_metadata(&meta, vec!["proof".into()]).expect("finding");
88        assert_eq!(finding.name, "Check");
89        assert_eq!(finding.severity, Severity::Medium);
90        assert_eq!(finding.evidence, vec!["proof"]);
91    }
92
93    #[test]
94    fn from_metadata_without_name_is_none() {
95        let meta = CheckMetadata {
96            description: Some("desc".into()),
97            ..Default::default()
98        };
99        assert!(Finding::from_metadata(&meta, vec![]).is_none());
100    }
101}