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