| 
 | 1 | +use std::fmt::{Display, Formatter};  | 
 | 2 | + | 
 | 3 | +use markdown_table::{Heading, HeadingAlignment, MarkdownTable};  | 
 | 4 | + | 
 | 5 | +use crate::domain::scanresult::{scan_result::ScanResult, severity::Severity};  | 
 | 6 | + | 
 | 7 | +#[derive(Clone, Debug, Default)]  | 
 | 8 | +pub struct FixablePackage {  | 
 | 9 | +    pub name: String,  | 
 | 10 | +    pub package_type: String,  | 
 | 11 | +    pub version: String,  | 
 | 12 | +    pub suggested_fix: Option<String>,  | 
 | 13 | +    pub vulnerabilities: FixablePackageVulnerabilities,  | 
 | 14 | +    pub exploits: u32,  | 
 | 15 | +}  | 
 | 16 | + | 
 | 17 | +#[derive(Clone, Debug, Default)]  | 
 | 18 | +pub struct FixablePackageVulnerabilities {  | 
 | 19 | +    pub critical: u32,  | 
 | 20 | +    pub high: u32,  | 
 | 21 | +    pub medium: u32,  | 
 | 22 | +    pub low: u32,  | 
 | 23 | +    pub negligible: u32,  | 
 | 24 | +}  | 
 | 25 | + | 
 | 26 | +#[derive(Clone, Debug, Default)]  | 
 | 27 | +pub struct FixablePackageTable(pub Vec<FixablePackage>);  | 
 | 28 | + | 
 | 29 | +impl From<&ScanResult> for FixablePackageTable {  | 
 | 30 | +    fn from(value: &ScanResult) -> Self {  | 
 | 31 | +        FixablePackageTable(  | 
 | 32 | +            value  | 
 | 33 | +                .packages()  | 
 | 34 | +                .into_iter()  | 
 | 35 | +                .filter(|p| p.vulnerabilities().iter().any(|v| v.fixable()))  | 
 | 36 | +                .map(|p| {  | 
 | 37 | +                    let mut vulns = FixablePackageVulnerabilities::default();  | 
 | 38 | +                    let mut exploits = 0;  | 
 | 39 | +                    for v in p.vulnerabilities() {  | 
 | 40 | +                        if v.exploitable() {  | 
 | 41 | +                            exploits += 1;  | 
 | 42 | +                        }  | 
 | 43 | +                        match v.severity() {  | 
 | 44 | +                            Severity::Critical => vulns.critical += 1,  | 
 | 45 | +                            Severity::High => vulns.high += 1,  | 
 | 46 | +                            Severity::Medium => vulns.medium += 1,  | 
 | 47 | +                            Severity::Low => vulns.low += 1,  | 
 | 48 | +                            Severity::Negligible => vulns.negligible += 1,  | 
 | 49 | +                            Severity::Unknown => {}  | 
 | 50 | +                        }  | 
 | 51 | +                    }  | 
 | 52 | + | 
 | 53 | +                    FixablePackage {  | 
 | 54 | +                        name: p.name().to_string(),  | 
 | 55 | +                        package_type: p.package_type().to_string(),  | 
 | 56 | +                        version: p.version().to_string(),  | 
 | 57 | +                        suggested_fix: p  | 
 | 58 | +                            .vulnerabilities()  | 
 | 59 | +                            .iter()  | 
 | 60 | +                            .find_map(|v| v.fix_version().map(|s| s.to_string())),  | 
 | 61 | +                        vulnerabilities: vulns,  | 
 | 62 | +                        exploits,  | 
 | 63 | +                    }  | 
 | 64 | +                })  | 
 | 65 | +                .collect(),  | 
 | 66 | +        )  | 
 | 67 | +    }  | 
 | 68 | +}  | 
 | 69 | + | 
 | 70 | +impl Display for FixablePackageTable {  | 
 | 71 | +    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {  | 
 | 72 | +        if self.0.is_empty() {  | 
 | 73 | +            return f.write_str("");  | 
 | 74 | +        }  | 
 | 75 | + | 
 | 76 | +        let headers = vec![  | 
 | 77 | +            Heading::new("PACKAGE".to_string(), Some(HeadingAlignment::Left)),  | 
 | 78 | +            Heading::new("TYPE".to_string(), Some(HeadingAlignment::Center)),  | 
 | 79 | +            Heading::new("VERSION".to_string(), Some(HeadingAlignment::Left)),  | 
 | 80 | +            Heading::new("SUGGESTED FIX".to_string(), Some(HeadingAlignment::Left)),  | 
 | 81 | +            Heading::new("CRITICAL".to_string(), Some(HeadingAlignment::Center)),  | 
 | 82 | +            Heading::new("HIGH".to_string(), Some(HeadingAlignment::Center)),  | 
 | 83 | +            Heading::new("MEDIUM".to_string(), Some(HeadingAlignment::Center)),  | 
 | 84 | +            Heading::new("LOW".to_string(), Some(HeadingAlignment::Center)),  | 
 | 85 | +            Heading::new("NEGLIGIBLE".to_string(), Some(HeadingAlignment::Center)),  | 
 | 86 | +            Heading::new("EXPLOIT".to_string(), Some(HeadingAlignment::Center)),  | 
 | 87 | +        ];  | 
 | 88 | + | 
 | 89 | +        let data = self  | 
 | 90 | +            .0  | 
 | 91 | +            .iter()  | 
 | 92 | +            .map(|p| {  | 
 | 93 | +                vec![  | 
 | 94 | +                    p.name.clone(),  | 
 | 95 | +                    p.package_type.clone(),  | 
 | 96 | +                    p.version.clone(),  | 
 | 97 | +                    p.suggested_fix.clone().unwrap_or_default(),  | 
 | 98 | +                    if p.vulnerabilities.critical > 0 {  | 
 | 99 | +                        p.vulnerabilities.critical.to_string()  | 
 | 100 | +                    } else {  | 
 | 101 | +                        "-".to_string()  | 
 | 102 | +                    },  | 
 | 103 | +                    if p.vulnerabilities.high > 0 {  | 
 | 104 | +                        p.vulnerabilities.high.to_string()  | 
 | 105 | +                    } else {  | 
 | 106 | +                        "-".to_string()  | 
 | 107 | +                    },  | 
 | 108 | +                    if p.vulnerabilities.medium > 0 {  | 
 | 109 | +                        p.vulnerabilities.medium.to_string()  | 
 | 110 | +                    } else {  | 
 | 111 | +                        "-".to_string()  | 
 | 112 | +                    },  | 
 | 113 | +                    if p.vulnerabilities.low > 0 {  | 
 | 114 | +                        p.vulnerabilities.low.to_string()  | 
 | 115 | +                    } else {  | 
 | 116 | +                        "-".to_string()  | 
 | 117 | +                    },  | 
 | 118 | +                    if p.vulnerabilities.negligible > 0 {  | 
 | 119 | +                        p.vulnerabilities.negligible.to_string()  | 
 | 120 | +                    } else {  | 
 | 121 | +                        "-".to_string()  | 
 | 122 | +                    },  | 
 | 123 | +                    if p.exploits > 0 {  | 
 | 124 | +                        p.exploits.to_string()  | 
 | 125 | +                    } else {  | 
 | 126 | +                        "-".to_string()  | 
 | 127 | +                    },  | 
 | 128 | +                ]  | 
 | 129 | +            })  | 
 | 130 | +            .collect();  | 
 | 131 | + | 
 | 132 | +        let mut table = MarkdownTable::new(data);  | 
 | 133 | +        table.with_headings(headers);  | 
 | 134 | + | 
 | 135 | +        let format = format!(  | 
 | 136 | +            "\n### Fixable Packages\n{}",  | 
 | 137 | +            table.as_markdown().unwrap_or_default()  | 
 | 138 | +        );  | 
 | 139 | + | 
 | 140 | +        f.write_str(&format)  | 
 | 141 | +    }  | 
 | 142 | +}  | 
0 commit comments