use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ScanVerdict {
Pass,
Warn,
Quarantine,
Block,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Severity {
Info,
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanFinding {
pub id: Uuid,
pub scanner: String,
pub severity: Severity,
pub category: String,
pub message: String,
pub evidence: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanResult {
pub verdict: ScanVerdict,
pub findings: Vec<ScanFinding>,
pub worst_severity: Severity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalizationPolicy {
pub enabled: bool,
pub max_artifact_size_bytes: usize,
pub block_threshold: Severity,
pub quarantine_threshold: Severity,
pub redact_secrets: bool,
}
impl Default for ExternalizationPolicy {
fn default() -> Self {
Self {
enabled: true,
max_artifact_size_bytes: 50 * 1024 * 1024, block_threshold: Severity::Critical,
quarantine_threshold: Severity::High,
redact_secrets: true,
}
}
}
impl std::fmt::Display for ScanVerdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pass => write!(f, "pass"),
Self::Warn => write!(f, "warn"),
Self::Quarantine => write!(f, "quarantine"),
Self::Block => write!(f, "block"),
}
}
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Info => write!(f, "info"),
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::Critical => write!(f, "critical"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn severity_ordering() {
assert!(Severity::Info < Severity::Low);
assert!(Severity::Low < Severity::Medium);
assert!(Severity::Medium < Severity::High);
assert!(Severity::High < Severity::Critical);
}
#[test]
fn verdict_ordering() {
assert!(ScanVerdict::Pass < ScanVerdict::Warn);
assert!(ScanVerdict::Warn < ScanVerdict::Quarantine);
assert!(ScanVerdict::Quarantine < ScanVerdict::Block);
}
#[test]
fn default_policy() {
let policy = ExternalizationPolicy::default();
assert!(policy.enabled);
assert!(policy.redact_secrets);
assert_eq!(policy.block_threshold, Severity::Critical);
}
#[test]
fn serde_roundtrip() {
let policy = ExternalizationPolicy::default();
let json = serde_json::to_string(&policy).unwrap();
let back: ExternalizationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(back.block_threshold, Severity::Critical);
}
#[test]
fn severity_display() {
assert_eq!(Severity::Info.to_string(), "info");
assert_eq!(Severity::Low.to_string(), "low");
assert_eq!(Severity::Medium.to_string(), "medium");
assert_eq!(Severity::High.to_string(), "high");
assert_eq!(Severity::Critical.to_string(), "critical");
}
#[test]
fn verdict_display() {
assert_eq!(ScanVerdict::Pass.to_string(), "pass");
assert_eq!(ScanVerdict::Warn.to_string(), "warn");
assert_eq!(ScanVerdict::Quarantine.to_string(), "quarantine");
assert_eq!(ScanVerdict::Block.to_string(), "block");
}
#[test]
fn scan_finding_serde() {
let finding = ScanFinding {
id: uuid::Uuid::new_v4(),
scanner: "test".into(),
severity: Severity::High,
category: "test_cat".into(),
message: "test msg".into(),
evidence: Some("evidence".into()),
};
let json = serde_json::to_string(&finding).unwrap();
let back: ScanFinding = serde_json::from_str(&json).unwrap();
assert_eq!(back.severity, Severity::High);
assert_eq!(back.category, "test_cat");
}
#[test]
fn scan_result_serde() {
let result = ScanResult {
verdict: ScanVerdict::Block,
findings: vec![],
worst_severity: Severity::Critical,
};
let json = serde_json::to_string(&result).unwrap();
let back: ScanResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.verdict, ScanVerdict::Block);
}
}