use std::collections::BTreeMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::control::{ControlFinding, ControlId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SeverityLabels {
pub info: String,
pub warning: String,
pub error: String,
}
impl Default for SeverityLabels {
fn default() -> Self {
Self {
info: "compliant".to_string(),
warning: "observation".to_string(),
error: "exception".to_string(),
}
}
}
impl SeverityLabels {
pub fn label_for(&self, severity: FindingSeverity) -> &str {
match severity {
FindingSeverity::Info => &self.info,
FindingSeverity::Warning => &self.warning,
FindingSeverity::Error => &self.error,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FindingSeverity {
Info,
Warning,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GateDecision {
Pass,
Review,
Fail,
}
impl GateDecision {
pub fn as_str(&self) -> &'static str {
match self {
Self::Pass => "pass",
Self::Review => "review",
Self::Fail => "fail",
}
}
}
impl fmt::Display for GateDecision {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileOutcome {
pub control_id: ControlId,
pub severity: FindingSeverity,
pub decision: GateDecision,
pub rationale: String,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub annotations: BTreeMap<String, String>,
}
pub trait ControlProfile {
fn name(&self) -> &str;
fn map(&self, finding: &ControlFinding) -> ProfileOutcome;
fn severity_labels(&self) -> SeverityLabels {
SeverityLabels::default()
}
}
pub fn apply_profile(
profile: &dyn ControlProfile,
findings: &[ControlFinding],
) -> Vec<ProfileOutcome> {
findings
.iter()
.map(|finding| profile.map(finding))
.collect()
}