libverify_core/
profile.rs1use std::collections::BTreeMap;
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6use crate::control::{ControlFinding, ControlId};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct SeverityLabels {
11 pub info: String,
12 pub warning: String,
13 pub error: String,
14}
15
16impl Default for SeverityLabels {
17 fn default() -> Self {
18 Self {
19 info: "compliant".to_string(),
20 warning: "observation".to_string(),
21 error: "exception".to_string(),
22 }
23 }
24}
25
26impl SeverityLabels {
27 pub fn label_for(&self, severity: FindingSeverity) -> &str {
28 match severity {
29 FindingSeverity::Info => &self.info,
30 FindingSeverity::Warning => &self.warning,
31 FindingSeverity::Error => &self.error,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum FindingSeverity {
40 Info,
41 Warning,
42 Error,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum GateDecision {
49 Pass,
50 Review,
51 Fail,
52}
53
54impl GateDecision {
55 pub fn as_str(&self) -> &'static str {
56 match self {
57 Self::Pass => "pass",
58 Self::Review => "review",
59 Self::Fail => "fail",
60 }
61 }
62}
63
64impl fmt::Display for GateDecision {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str(self.as_str())
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ProfileOutcome {
73 pub control_id: ControlId,
74 pub severity: FindingSeverity,
75 pub decision: GateDecision,
76 pub rationale: String,
77 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
80 pub annotations: BTreeMap<String, String>,
81}
82
83pub trait ControlProfile {
85 fn name(&self) -> &str;
86 fn map(&self, finding: &ControlFinding) -> ProfileOutcome;
87 fn severity_labels(&self) -> SeverityLabels {
88 SeverityLabels::default()
89 }
90}
91
92pub fn apply_profile(
94 profile: &dyn ControlProfile,
95 findings: &[ControlFinding],
96) -> Vec<ProfileOutcome> {
97 findings
98 .iter()
99 .map(|finding| profile.map(finding))
100 .collect()
101}