Skip to main content

ruso_runtime/runtime/
report.rs

1use crate::contract::Severity;
2use crate::runtime::spec::CheckMetadata;
3
4#[derive(Debug, Clone)]
5pub struct Finding {
6    pub name: String,
7    pub description: Option<String>,
8    pub impact: Option<String>,
9    pub severity: Severity,
10    pub author: Option<String>,
11    pub cve: Vec<String>,
12    pub cwe: Vec<String>,
13    pub references: Vec<String>,
14    pub cvss: Vec<String>,
15    pub cvss_score: Vec<String>,
16    pub mitigation: Option<String>,
17    pub evidence: Vec<String>,
18}
19
20impl Finding {
21    /// Build the single finding for a script from its metadata block.
22    pub fn from_metadata(metadata: &CheckMetadata, evidence: Vec<String>) -> Option<Self> {
23        let name = metadata
24            .name
25            .clone()
26            .or_else(|| metadata.report_title.clone())?;
27        Some(Self {
28            name,
29            description: metadata.description.clone(),
30            impact: metadata.impact.clone(),
31            severity: metadata.severity.clone().unwrap_or(Severity::Info),
32            author: metadata.author.clone(),
33            cve: metadata.cve.clone(),
34            cwe: metadata.cwe.clone(),
35            references: metadata.references.clone(),
36            cvss: metadata.cvss.clone(),
37            cvss_score: metadata.cvss_score.clone(),
38            mitigation: metadata.mitigation.clone(),
39            evidence,
40        })
41    }
42}
43
44#[derive(Debug, Clone, Default)]
45pub struct Report {
46    pub findings: Vec<Finding>,
47}
48
49impl Report {
50    /// Replace any prior finding — at most one per script execution.
51    pub fn set_finding(&mut self, finding: Finding) {
52        self.findings.clear();
53        self.findings.push(finding);
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use crate::contract::Severity;
60    use crate::runtime::spec::CheckMetadata;
61
62    use super::Finding;
63
64    #[test]
65    fn from_metadata_requires_name_or_report_title() {
66        let meta = CheckMetadata {
67            name: Some("Check".into()),
68            description: Some("desc".into()),
69            severity: Some(Severity::Medium),
70            ..Default::default()
71        };
72        let finding = Finding::from_metadata(&meta, vec!["proof".into()]).expect("finding");
73        assert_eq!(finding.name, "Check");
74        assert_eq!(finding.severity, Severity::Medium);
75        assert_eq!(finding.evidence, vec!["proof"]);
76    }
77
78    #[test]
79    fn from_metadata_uses_report_title_fallback() {
80        let meta = CheckMetadata {
81            report_title: Some("Legacy title".into()),
82            ..Default::default()
83        };
84        let finding = Finding::from_metadata(&meta, vec![]).expect("finding");
85        assert_eq!(finding.name, "Legacy title");
86        assert_eq!(finding.severity, Severity::Info);
87    }
88}