Skip to main content

systemprompt_traits/
validation_report.rs

1//! Unified validation report types for startup validation.
2
3use std::path::PathBuf;
4
5#[derive(Debug, Clone)]
6pub struct ValidationError {
7    pub field: String,
8    pub message: String,
9    pub path: Option<PathBuf>,
10    pub suggestion: Option<String>,
11}
12
13impl ValidationError {
14    #[must_use]
15    pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
16        Self {
17            field: field.into(),
18            message: message.into(),
19            path: None,
20            suggestion: None,
21        }
22    }
23
24    #[must_use]
25    pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
26        self.path = Some(path.into());
27        self
28    }
29
30    #[must_use]
31    pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
32        self.suggestion = Some(suggestion.into());
33        self
34    }
35}
36
37impl std::fmt::Display for ValidationError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(f, "{}\n  {}", self.field, self.message)?;
40        if let Some(ref path) = self.path {
41            write!(f, "\n  Path: {}", path.display())?;
42        }
43        if let Some(ref suggestion) = self.suggestion {
44            write!(f, "\n  To fix: {}", suggestion)?;
45        }
46        Ok(())
47    }
48}
49
50#[derive(Debug, Clone)]
51pub struct ValidationWarning {
52    pub field: String,
53    pub message: String,
54    pub suggestion: Option<String>,
55}
56
57impl ValidationWarning {
58    #[must_use]
59    pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
60        Self {
61            field: field.into(),
62            message: message.into(),
63            suggestion: None,
64        }
65    }
66
67    #[must_use]
68    pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
69        self.suggestion = Some(suggestion.into());
70        self
71    }
72}
73
74#[derive(Debug, Clone, Default)]
75pub struct ValidationReport {
76    pub domain: String,
77    pub errors: Vec<ValidationError>,
78    pub warnings: Vec<ValidationWarning>,
79}
80
81impl ValidationReport {
82    #[must_use]
83    pub fn new(domain: impl Into<String>) -> Self {
84        Self {
85            domain: domain.into(),
86            errors: Vec::new(),
87            warnings: Vec::new(),
88        }
89    }
90
91    pub fn add_error(&mut self, error: ValidationError) {
92        self.errors.push(error);
93    }
94
95    pub fn add_warning(&mut self, warning: ValidationWarning) {
96        self.warnings.push(warning);
97    }
98
99    pub fn has_errors(&self) -> bool {
100        !self.errors.is_empty()
101    }
102
103    pub fn has_warnings(&self) -> bool {
104        !self.warnings.is_empty()
105    }
106
107    pub fn is_clean(&self) -> bool {
108        self.errors.is_empty() && self.warnings.is_empty()
109    }
110
111    pub fn merge(&mut self, other: Self) {
112        self.errors.extend(other.errors);
113        self.warnings.extend(other.warnings);
114    }
115}
116
117#[derive(Debug, Clone, Default)]
118pub struct StartupValidationReport {
119    pub profile_path: Option<PathBuf>,
120    pub domains: Vec<ValidationReport>,
121    pub extensions: Vec<ValidationReport>,
122}
123
124impl StartupValidationReport {
125    #[must_use]
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    #[must_use]
131    pub fn with_profile_path(mut self, path: impl Into<PathBuf>) -> Self {
132        self.profile_path = Some(path.into());
133        self
134    }
135
136    pub fn add_domain(&mut self, report: ValidationReport) {
137        self.domains.push(report);
138    }
139
140    pub fn add_extension(&mut self, report: ValidationReport) {
141        self.extensions.push(report);
142    }
143
144    pub fn has_errors(&self) -> bool {
145        self.domains.iter().any(ValidationReport::has_errors)
146            || self.extensions.iter().any(ValidationReport::has_errors)
147    }
148
149    pub fn has_warnings(&self) -> bool {
150        self.domains.iter().any(ValidationReport::has_warnings)
151            || self.extensions.iter().any(ValidationReport::has_warnings)
152    }
153
154    pub fn error_count(&self) -> usize {
155        self.domains.iter().map(|r| r.errors.len()).sum::<usize>()
156            + self
157                .extensions
158                .iter()
159                .map(|r| r.errors.len())
160                .sum::<usize>()
161    }
162
163    pub fn warning_count(&self) -> usize {
164        self.domains.iter().map(|r| r.warnings.len()).sum::<usize>()
165            + self
166                .extensions
167                .iter()
168                .map(|r| r.warnings.len())
169                .sum::<usize>()
170    }
171}
172
173impl std::fmt::Display for StartupValidationReport {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        write!(
176            f,
177            "{} error(s), {} warning(s)",
178            self.error_count(),
179            self.warning_count()
180        )
181    }
182}
183
184#[derive(Debug, thiserror::Error)]
185#[error("Startup validation failed with {0}")]
186pub struct StartupValidationError(pub StartupValidationReport);
187
188impl From<StartupValidationReport> for StartupValidationError {
189    fn from(report: StartupValidationReport) -> Self {
190        Self(report)
191    }
192}