use std::fmt;
#[derive(Debug, Clone)]
pub struct Score {
pub total: i32,
pub factors: Vec<ScoreFactor>,
pub severity: Severity,
pub killed: bool,
pub killed_by: Option<String>,
}
impl Score {
pub fn new(factors: Vec<ScoreFactor>) -> Self {
let total: i32 = factors.iter().map(|f| f.contribution).sum();
let killed = factors.iter().any(|f| f.contribution <= -100);
let killed_by = if killed {
factors
.iter()
.filter(|f| f.contribution <= -100)
.map(|f| f.name.clone())
.next()
} else {
None
};
let severity = Severity::from_score(total);
Self {
total,
factors,
severity,
killed,
killed_by,
}
}
pub fn passes_threshold(&self, threshold: i32) -> bool {
!self.killed && self.total >= threshold
}
pub fn from_total(total: i32) -> Self {
Self {
total,
factors: Vec::new(),
severity: Severity::from_score(total),
killed: false,
killed_by: None,
}
}
}
impl Default for Score {
fn default() -> Self {
Self::new(Vec::new())
}
}
impl fmt::Display for Score {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.total, self.severity)
}
}
#[derive(Debug, Clone)]
pub struct ScoreFactor {
pub name: String,
pub category: FactorCategory,
pub contribution: i32,
pub reason: String,
pub evidence: Option<String>,
}
impl ScoreFactor {
pub fn new(
name: impl Into<String>,
category: FactorCategory,
contribution: i32,
reason: impl Into<String>,
) -> Self {
Self {
name: name.into(),
category,
contribution,
reason: reason.into(),
evidence: None,
}
}
pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
self.evidence = Some(evidence.into());
self
}
pub fn kill(name: impl Into<String>, reason: impl Into<String>) -> Self {
Self::new(name, FactorCategory::Suppression, -100, reason)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactorCategory {
Base,
Name,
Context,
Consequence,
RightHandSide,
ValueAnalysis,
Pattern,
Suppression,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Severity {
#[default]
None,
Info,
Low,
Medium,
High,
Critical,
}
impl Severity {
pub fn from_score(score: i32) -> Self {
match score {
s if s <= 0 => Self::None,
s if s < 30 => Self::Info,
s if s < 50 => Self::Low,
s if s < 70 => Self::Medium,
s if s < 90 => Self::High,
_ => Self::Critical,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::None => "none",
Self::Info => "info",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Critical => "critical",
}
}
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}