1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum ValidationErrorType {
10 MissingRequired,
12 TypeMismatch { expected: String, actual: String },
14 UnknownField,
16}
17
18impl fmt::Display for ValidationErrorType {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 match self {
21 ValidationErrorType::MissingRequired => write!(f, "missing required field"),
22 ValidationErrorType::TypeMismatch { expected, actual } => {
23 write!(f, "expected {}, got {}", expected, actual)
24 }
25 ValidationErrorType::UnknownField => write!(f, "unknown field"),
26 }
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ValidationError {
33 pub line: usize,
35 pub path: String,
37 pub error_type: ValidationErrorType,
39 pub message: String,
41}
42
43impl ValidationError {
44 pub fn missing_required(line: usize, path: &str) -> Self {
46 Self {
47 line,
48 path: path.to_string(),
49 error_type: ValidationErrorType::MissingRequired,
50 message: format!("Field '{}' is REQUIRED but missing", path),
51 }
52 }
53
54 pub fn type_mismatch(
56 line: usize,
57 path: &str,
58 expected: &str,
59 actual: &str,
60 value: &str,
61 ) -> Self {
62 Self {
63 line,
64 path: path.to_string(),
65 error_type: ValidationErrorType::TypeMismatch {
66 expected: expected.to_string(),
67 actual: actual.to_string(),
68 },
69 message: format!(
70 "Field '{}' expected {}, got {} (\"{}\")",
71 path, expected, actual, value
72 ),
73 }
74 }
75
76 pub fn unknown_field(line: usize, path: &str) -> Self {
78 Self {
79 line,
80 path: path.to_string(),
81 error_type: ValidationErrorType::UnknownField,
82 message: format!("Unknown field '{}' not in schema", path),
83 }
84 }
85}
86
87impl fmt::Display for ValidationError {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(f, "Line {}: {}", self.line, self.message)
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ValidationResult {
96 pub valid: bool,
98 pub error_count: usize,
100 pub errors: Vec<ValidationError>,
102 #[serde(skip_serializing_if = "Vec::is_empty")]
104 pub warnings: Vec<ValidationError>,
105}
106
107impl ValidationResult {
108 pub fn new() -> Self {
110 Self {
111 valid: true,
112 error_count: 0,
113 errors: Vec::new(),
114 warnings: Vec::new(),
115 }
116 }
117
118 pub fn add_error(&mut self, error: ValidationError) {
120 self.valid = false;
121 self.error_count += 1;
122 self.errors.push(error);
123 }
124
125 pub fn add_warning(&mut self, warning: ValidationError) {
127 self.warnings.push(warning);
128 }
129
130 pub fn reached_max_errors(&self, max_errors: usize) -> bool {
132 self.error_count >= max_errors
133 }
134}
135
136impl Default for ValidationResult {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_validation_error_display() {
148 let err = ValidationError::missing_required(42, "user_id");
149 assert!(err.to_string().contains("Line 42"));
150 assert!(err.to_string().contains("REQUIRED"));
151
152 let err = ValidationError::type_mismatch(10, "age", "INTEGER", "STRING", "thirty");
153 assert!(err.to_string().contains("expected INTEGER"));
154 assert!(err.to_string().contains("got STRING"));
155
156 let err = ValidationError::unknown_field(5, "legacy_field");
157 assert!(err.to_string().contains("Unknown field"));
158 }
159
160 #[test]
161 fn test_validation_result() {
162 let mut result = ValidationResult::new();
163 assert!(result.valid);
164 assert_eq!(result.error_count, 0);
165
166 result.add_error(ValidationError::missing_required(1, "id"));
167 assert!(!result.valid);
168 assert_eq!(result.error_count, 1);
169
170 result.add_warning(ValidationError::unknown_field(2, "extra"));
171 assert_eq!(result.warnings.len(), 1);
172 }
173
174 #[test]
177 fn test_validation_error_type_display() {
178 let missing = ValidationErrorType::MissingRequired;
179 assert_eq!(missing.to_string(), "missing required field");
180
181 let type_mismatch = ValidationErrorType::TypeMismatch {
182 expected: "INTEGER".to_string(),
183 actual: "STRING".to_string(),
184 };
185 assert_eq!(type_mismatch.to_string(), "expected INTEGER, got STRING");
186
187 let unknown = ValidationErrorType::UnknownField;
188 assert_eq!(unknown.to_string(), "unknown field");
189 }
190
191 #[test]
192 fn test_validation_error_missing_required() {
193 let err = ValidationError::missing_required(10, "user.name");
194
195 assert_eq!(err.line, 10);
196 assert_eq!(err.path, "user.name");
197 assert_eq!(err.error_type, ValidationErrorType::MissingRequired);
198 assert!(err.message.contains("REQUIRED"));
199 assert!(err.message.contains("user.name"));
200 }
201
202 #[test]
203 fn test_validation_error_type_mismatch() {
204 let err = ValidationError::type_mismatch(5, "age", "INTEGER", "STRING", "twenty");
205
206 assert_eq!(err.line, 5);
207 assert_eq!(err.path, "age");
208 match &err.error_type {
209 ValidationErrorType::TypeMismatch { expected, actual } => {
210 assert_eq!(expected, "INTEGER");
211 assert_eq!(actual, "STRING");
212 }
213 _ => panic!("Expected TypeMismatch"),
214 }
215 assert!(err.message.contains("expected INTEGER"));
216 assert!(err.message.contains("got STRING"));
217 assert!(err.message.contains("twenty"));
218 }
219
220 #[test]
221 fn test_validation_error_unknown_field() {
222 let err = ValidationError::unknown_field(3, "legacy_data.old_field");
223
224 assert_eq!(err.line, 3);
225 assert_eq!(err.path, "legacy_data.old_field");
226 assert_eq!(err.error_type, ValidationErrorType::UnknownField);
227 assert!(err.message.contains("Unknown field"));
228 assert!(err.message.contains("not in schema"));
229 }
230
231 #[test]
232 fn test_validation_error_display_format() {
233 let err = ValidationError::missing_required(42, "required_field");
234 let display_str = err.to_string();
235
236 assert!(display_str.contains("Line 42"));
237 assert!(display_str.contains("required_field"));
238 }
239
240 #[test]
241 fn test_validation_result_default() {
242 let result = ValidationResult::default();
243
244 assert!(result.valid);
245 assert_eq!(result.error_count, 0);
246 assert!(result.errors.is_empty());
247 assert!(result.warnings.is_empty());
248 }
249
250 #[test]
251 fn test_validation_result_reached_max_errors() {
252 let mut result = ValidationResult::new();
253
254 assert!(!result.reached_max_errors(5));
255
256 for i in 0..5 {
257 result.add_error(ValidationError::missing_required(i, "field"));
258 }
259
260 assert!(result.reached_max_errors(5));
261 assert!(!result.reached_max_errors(6));
262 }
263
264 #[test]
265 fn test_validation_result_multiple_errors() {
266 let mut result = ValidationResult::new();
267
268 result.add_error(ValidationError::missing_required(1, "a"));
269 result.add_error(ValidationError::type_mismatch(2, "b", "INT", "STR", "x"));
270 result.add_error(ValidationError::unknown_field(3, "c"));
271
272 assert!(!result.valid);
273 assert_eq!(result.error_count, 3);
274 assert_eq!(result.errors.len(), 3);
275 }
276
277 #[test]
278 fn test_validation_result_multiple_warnings() {
279 let mut result = ValidationResult::new();
280
281 result.add_warning(ValidationError::unknown_field(1, "extra1"));
282 result.add_warning(ValidationError::unknown_field(2, "extra2"));
283
284 assert!(result.valid);
286 assert_eq!(result.error_count, 0);
287 assert_eq!(result.warnings.len(), 2);
288 }
289
290 #[test]
291 fn test_validation_error_serialization() {
292 let err = ValidationError::type_mismatch(1, "field", "INTEGER", "BOOLEAN", "true");
293 let json = serde_json::to_string(&err).unwrap();
294
295 assert!(json.contains("\"line\":1"));
296 assert!(json.contains("\"path\":\"field\""));
297 assert!(json.contains("type_mismatch"));
298 }
299
300 #[test]
301 fn test_validation_result_serialization() {
302 let mut result = ValidationResult::new();
303 result.add_error(ValidationError::missing_required(1, "id"));
304
305 let json = serde_json::to_string(&result).unwrap();
306
307 assert!(json.contains("\"valid\":false"));
308 assert!(json.contains("\"error_count\":1"));
309 }
311
312 #[test]
313 fn test_validation_error_type_equality() {
314 let a = ValidationErrorType::MissingRequired;
315 let b = ValidationErrorType::MissingRequired;
316 assert_eq!(a, b);
317
318 let c = ValidationErrorType::TypeMismatch {
319 expected: "A".to_string(),
320 actual: "B".to_string(),
321 };
322 let d = ValidationErrorType::TypeMismatch {
323 expected: "A".to_string(),
324 actual: "B".to_string(),
325 };
326 assert_eq!(c, d);
327
328 let e = ValidationErrorType::TypeMismatch {
329 expected: "A".to_string(),
330 actual: "C".to_string(),
331 };
332 assert_ne!(c, e);
333 }
334}