Skip to main content

libverify_core/
profile.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::control::{ControlFinding, ControlId};
6
7/// Policy-specific display labels for severity levels.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct SeverityLabels {
10    pub info: String,
11    pub warning: String,
12    pub error: String,
13}
14
15impl Default for SeverityLabels {
16    fn default() -> Self {
17        Self {
18            info: "compliant".to_string(),
19            warning: "observation".to_string(),
20            error: "exception".to_string(),
21        }
22    }
23}
24
25impl SeverityLabels {
26    pub fn label_for(&self, severity: FindingSeverity) -> &str {
27        match severity {
28            FindingSeverity::Info => &self.info,
29            FindingSeverity::Warning => &self.warning,
30            FindingSeverity::Error => &self.error,
31        }
32    }
33}
34
35/// Severity level assigned to a control finding by a profile.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum FindingSeverity {
39    Info,
40    Warning,
41    Error,
42}
43
44/// Gate outcome that determines whether a pipeline stage may proceed.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum GateDecision {
48    Pass,
49    Review,
50    Fail,
51}
52
53impl GateDecision {
54    pub fn as_str(&self) -> &'static str {
55        match self {
56            Self::Pass => "pass",
57            Self::Review => "review",
58            Self::Fail => "fail",
59        }
60    }
61}
62
63impl fmt::Display for GateDecision {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.write_str(self.as_str())
66    }
67}
68
69/// The profile-mapped result for a single control finding.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct ProfileOutcome {
72    pub control_id: ControlId,
73    pub severity: FindingSeverity,
74    pub decision: GateDecision,
75    pub rationale: String,
76}
77
78/// Maps raw control findings to severity and gate decisions for a given policy.
79pub trait ControlProfile {
80    fn name(&self) -> &str;
81    fn map(&self, finding: &ControlFinding) -> ProfileOutcome;
82    fn severity_labels(&self) -> SeverityLabels {
83        SeverityLabels::default()
84    }
85}
86
87/// Applies a profile to all findings and returns the mapped outcomes.
88pub fn apply_profile(
89    profile: &dyn ControlProfile,
90    findings: &[ControlFinding],
91) -> Vec<ProfileOutcome> {
92    findings
93        .iter()
94        .map(|finding| profile.map(finding))
95        .collect()
96}