use serde::{Deserialize, Serialize};
use super::AuditHint;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeferredDefenseAudit {
pub declaration: crate::scan::DeferredDefense,
pub hint: AuditHint,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DeferredDefenseAuditReport {
pub audits: Vec<DeferredDefenseAudit>,
pub active_count: usize,
pub expired_count: usize,
pub stale_count: usize,
}
#[must_use]
pub fn audit_deferred_defenses(
scan: &crate::scan::ScanReport,
stale_grace_days: i64,
) -> DeferredDefenseAuditReport {
use chrono::Utc;
let today = Utc::now().date_naive();
let mut audits = Vec::new();
let mut active_count = 0usize;
let mut expired_count = 0usize;
let mut stale_count = 0usize;
for decl in &scan.deferred_defenses {
let hint = evaluate_deferred_defense_hint(decl, today, stale_grace_days);
match &hint {
AuditHint::AnergyActive
| AuditHint::ImmunosuppressActive
| AuditHint::PoxpartyActive
| AuditHint::OrientActive => {
active_count += 1;
}
AuditHint::AnergyCostimulationNotArrived
| AuditHint::ImmunosuppressExpired
| AuditHint::PoxpartyOutcomePending
| AuditHint::OrientPendingActionRequired => {
expired_count += 1;
}
AuditHint::AnergyStale | AuditHint::ImmunosuppressDurationCapExceeded => {
stale_count += 1;
}
_ => {}
}
audits.push(DeferredDefenseAudit {
declaration: decl.clone(),
hint,
});
}
DeferredDefenseAuditReport {
audits,
active_count,
expired_count,
stale_count,
}
}
fn evaluate_deferred_defense_hint(
decl: &crate::scan::DeferredDefense,
today: chrono::NaiveDate,
stale_grace_days: i64,
) -> AuditHint {
use crate::scan::DeferredDefenseKind;
match &decl.kind {
DeferredDefenseKind::Anergy => {
match decl.until.as_deref() {
None | Some("") => AuditHint::AnergyActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::AnergyActive,
Some(until) => {
let days_past = (today - until).num_days();
if days_past > stale_grace_days {
AuditHint::AnergyStale
} else {
AuditHint::AnergyCostimulationNotArrived
}
}
None => AuditHint::AnergyCostimulationNotArrived,
},
}
}
DeferredDefenseKind::Immunosuppress => {
if let (Some(since), Some(cap)) = (decl.since.as_deref(), decl.duration_cap) {
if let Some(since_date) = parse_iso_date(since) {
let elapsed = (today - since_date).num_days();
if let Ok(cap_days) = i64::try_from(cap) {
if elapsed > cap_days {
return AuditHint::ImmunosuppressDurationCapExceeded;
}
}
}
}
match decl.until.as_deref() {
None | Some("") => AuditHint::ImmunosuppressActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::ImmunosuppressActive,
_ => AuditHint::ImmunosuppressExpired,
},
}
}
DeferredDefenseKind::Poxparty => {
match decl.until.as_deref() {
None | Some("") => AuditHint::PoxpartyActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::PoxpartyActive,
_ => AuditHint::PoxpartyOutcomePending,
},
}
}
DeferredDefenseKind::Orient => {
match decl.until.as_deref() {
None | Some("") => AuditHint::OrientActive,
Some(s) => match parse_iso_date(s) {
Some(until) if until >= today => AuditHint::OrientActive,
_ => AuditHint::OrientPendingActionRequired,
},
}
}
}
}
fn parse_iso_date(s: &str) -> Option<chrono::NaiveDate> {
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()
}