Skip to main content

powerio_pkg/
validation.rs

1//! The package-level validation summary.
2
3use serde::{Deserialize, Serialize};
4
5use crate::diagnostics::{DiagnosticSeverity, StructuredDiagnostic};
6
7/// Overall validation status, ordered worst-last.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ValidationStatus {
11    Ok,
12    Info,
13    Warning,
14    Error,
15    Fatal,
16}
17
18impl ValidationStatus {
19    fn from_severity(s: DiagnosticSeverity) -> Self {
20        match s {
21            DiagnosticSeverity::Debug => ValidationStatus::Ok,
22            DiagnosticSeverity::Info => ValidationStatus::Info,
23            DiagnosticSeverity::Warning => ValidationStatus::Warning,
24            DiagnosticSeverity::Error => ValidationStatus::Error,
25            DiagnosticSeverity::Fatal => ValidationStatus::Fatal,
26        }
27    }
28}
29
30/// Counts per severity. All five are always present; zero where unused.
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
32pub struct ValidationCounts {
33    #[serde(default)]
34    pub fatal: u32,
35    #[serde(default)]
36    pub error: u32,
37    #[serde(default)]
38    pub warning: u32,
39    #[serde(default)]
40    pub info: u32,
41    #[serde(default)]
42    pub debug: u32,
43}
44
45impl ValidationCounts {
46    fn add(&mut self, s: DiagnosticSeverity) {
47        match s {
48            DiagnosticSeverity::Fatal => self.fatal += 1,
49            DiagnosticSeverity::Error => self.error += 1,
50            DiagnosticSeverity::Warning => self.warning += 1,
51            DiagnosticSeverity::Info => self.info += 1,
52            DiagnosticSeverity::Debug => self.debug += 1,
53        }
54    }
55}
56
57/// The status of one named validation pass.
58#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
59pub struct ValidationPass {
60    pub name: String,
61    pub status: ValidationStatus,
62}
63
64impl ValidationPass {
65    pub fn new(name: impl Into<String>, status: ValidationStatus) -> Self {
66        Self {
67            name: name.into(),
68            status,
69        }
70    }
71}
72
73/// A cheap-to-inspect summary of validation: an overall status, per-severity
74/// counts, and the named passes that ran.
75#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76pub struct ValidationSummary {
77    pub status: ValidationStatus,
78    pub counts: ValidationCounts,
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    pub passes: Vec<ValidationPass>,
81}
82
83impl ValidationSummary {
84    /// A clean pass with no findings.
85    pub fn ok() -> Self {
86        Self {
87            status: ValidationStatus::Ok,
88            counts: ValidationCounts::default(),
89            passes: Vec::new(),
90        }
91    }
92
93    /// Derive counts and the dominant status from a set of diagnostics.
94    pub fn from_diagnostics(diagnostics: &[StructuredDiagnostic]) -> Self {
95        let mut counts = ValidationCounts::default();
96        let mut status = ValidationStatus::Ok;
97        for d in diagnostics {
98            counts.add(d.severity);
99            status = status.max(ValidationStatus::from_severity(d.severity));
100        }
101        Self {
102            status,
103            counts,
104            passes: Vec::new(),
105        }
106    }
107
108    #[must_use]
109    pub fn with_passes(mut self, passes: Vec<ValidationPass>) -> Self {
110        self.passes = passes;
111        self
112    }
113}