Skip to main content

systemprompt_traits/
validation_report.rs

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