1use super::core::{
8 ErrorContext, ErrorSeverity, RecoveryError, RecoveryErrorKind, RecoverySuggestion,
9};
10
11pub struct ValidationErrorContext;
13
14impl ValidationErrorContext {
15 pub fn v2_format(
17 lsn_range: Option<(u64, u64)>,
18 expected_format: &str,
19 actual_format: Option<&str>,
20 ) -> ErrorContext {
21 let mut context = ErrorContext::default();
22 context.lsn_range = lsn_range;
23 context.recovery_state = Some("V2 Format Validation".to_string());
24
25 if let Some(format) = actual_format {
26 context
27 .metadata
28 .insert("expected_format".to_string(), expected_format.to_string());
29 context
30 .metadata
31 .insert("actual_format".to_string(), format.to_string());
32 } else {
33 context
34 .metadata
35 .insert("expected_format".to_string(), expected_format.to_string());
36 context
37 .metadata
38 .insert("actual_format".to_string(), "None".to_string());
39 }
40
41 context
42 }
43
44 pub fn checksum_validation(
46 lsn: u64,
47 expected_checksum: u64,
48 actual_checksum: u64,
49 algorithm: &str,
50 ) -> ErrorContext {
51 let mut context = ErrorContext::default();
52 context.lsn_range = Some((lsn, lsn));
53 context.recovery_state = Some("Checksum Validation".to_string());
54
55 context.metadata.insert("lsn".to_string(), lsn.to_string());
56 context.metadata.insert(
57 "expected_checksum".to_string(),
58 format!("{:x}", expected_checksum),
59 );
60 context.metadata.insert(
61 "actual_checksum".to_string(),
62 format!("{:x}", actual_checksum),
63 );
64 context
65 .metadata
66 .insert("checksum_algorithm".to_string(), algorithm.to_string());
67
68 context
69 }
70
71 pub fn consistency_validation(
73 node_count_mismatch: Option<(u64, u64)>,
74 edge_count_mismatch: Option<(u64, u64)>,
75 cluster_inconsistency: Option<(u64, u64)>,
76 ) -> ErrorContext {
77 let mut context = ErrorContext::default();
78 context.recovery_state = Some("Consistency Validation".to_string());
79
80 if let Some((expected, actual)) = node_count_mismatch {
81 context
82 .metadata
83 .insert("expected_node_count".to_string(), expected.to_string());
84 context
85 .metadata
86 .insert("actual_node_count".to_string(), actual.to_string());
87 }
88
89 if let Some((expected, actual)) = edge_count_mismatch {
90 context
91 .metadata
92 .insert("expected_edge_count".to_string(), expected.to_string());
93 context
94 .metadata
95 .insert("actual_edge_count".to_string(), actual.to_string());
96 }
97
98 if let Some((cluster_id, error_type)) = cluster_inconsistency {
99 context
100 .metadata
101 .insert("cluster_id".to_string(), cluster_id.to_string());
102 context
103 .metadata
104 .insert("cluster_error_type".to_string(), error_type.to_string());
105 }
106
107 context
108 }
109
110 pub fn structural_validation(
112 record_type: &str,
113 field_name: &str,
114 expected_type: &str,
115 actual_value: Option<&str>,
116 ) -> ErrorContext {
117 let mut context = ErrorContext::default();
118 context.recovery_state = Some("Structural Validation".to_string());
119
120 context
121 .metadata
122 .insert("record_type".to_string(), record_type.to_string());
123 context
124 .metadata
125 .insert("field_name".to_string(), field_name.to_string());
126 context
127 .metadata
128 .insert("expected_type".to_string(), expected_type.to_string());
129
130 if let Some(value) = actual_value {
131 context
132 .metadata
133 .insert("actual_value".to_string(), value.to_string());
134 } else {
135 context
136 .metadata
137 .insert("actual_value".to_string(), "None".to_string());
138 }
139
140 context
141 }
142
143 pub fn range_validation(
145 field_name: &str,
146 value: u64,
147 min_allowed: u64,
148 max_allowed: u64,
149 ) -> ErrorContext {
150 let mut context = ErrorContext::default();
151 context.recovery_state = Some("Range Validation".to_string());
152
153 context
154 .metadata
155 .insert("field_name".to_string(), field_name.to_string());
156 context
157 .metadata
158 .insert("value".to_string(), value.to_string());
159 context
160 .metadata
161 .insert("min_allowed".to_string(), min_allowed.to_string());
162 context
163 .metadata
164 .insert("max_allowed".to_string(), max_allowed.to_string());
165
166 context
167 }
168
169 pub fn dependency_validation(
171 dependent_type: &str,
172 dependency_type: &str,
173 dependency_id: u64,
174 missing_reference: bool,
175 ) -> ErrorContext {
176 let mut context = ErrorContext::default();
177 context.recovery_state = Some("Dependency Validation".to_string());
178
179 context
180 .metadata
181 .insert("dependent_type".to_string(), dependent_type.to_string());
182 context
183 .metadata
184 .insert("dependency_type".to_string(), dependency_type.to_string());
185 context
186 .metadata
187 .insert("dependency_id".to_string(), dependency_id.to_string());
188 context.metadata.insert(
189 "missing_reference".to_string(),
190 missing_reference.to_string(),
191 );
192
193 context
194 }
195}
196
197pub struct ValidationErrorFactory;
199
200impl ValidationErrorFactory {
201 pub fn v2_format_error(
203 message: impl Into<String>,
204 expected_format: &str,
205 actual_format: Option<&str>,
206 ) -> RecoveryError {
207 RecoveryError::new(RecoveryErrorKind::Validation, message)
208 .with_context(ValidationErrorContext::v2_format(
209 None,
210 expected_format,
211 actual_format,
212 ))
213 .with_recovery(RecoverySuggestion::ValidateWalFile)
214 .with_severity(ErrorSeverity::Error)
215 }
216
217 pub fn checksum_error(
219 lsn: u64,
220 expected_checksum: u64,
221 actual_checksum: u64,
222 algorithm: &str,
223 ) -> RecoveryError {
224 let message = format!(
225 "Checksum validation failed at LSN {}: expected {:x}, got {:x} ({})",
226 lsn, expected_checksum, actual_checksum, algorithm
227 );
228
229 RecoveryError::new(RecoveryErrorKind::Validation, message)
230 .with_context(ValidationErrorContext::checksum_validation(
231 lsn,
232 expected_checksum,
233 actual_checksum,
234 algorithm,
235 ))
236 .with_recovery(RecoverySuggestion::ForceRecovery)
237 .with_severity(ErrorSeverity::Error)
238 }
239
240 pub fn consistency_error(
242 message: impl Into<String>,
243 node_count_mismatch: Option<(u64, u64)>,
244 edge_count_mismatch: Option<(u64, u64)>,
245 ) -> RecoveryError {
246 RecoveryError::new(RecoveryErrorKind::Consistency, message)
247 .with_context(ValidationErrorContext::consistency_validation(
248 node_count_mismatch,
249 edge_count_mismatch,
250 None,
251 ))
252 .with_recovery(RecoverySuggestion::ForceRecovery)
253 .with_severity(ErrorSeverity::Critical)
254 }
255
256 pub fn structural_error(
258 message: impl Into<String>,
259 record_type: &str,
260 field_name: &str,
261 expected_type: &str,
262 ) -> RecoveryError {
263 RecoveryError::new(RecoveryErrorKind::Validation, message)
264 .with_context(ValidationErrorContext::structural_validation(
265 record_type,
266 field_name,
267 expected_type,
268 None,
269 ))
270 .with_recovery(RecoverySuggestion::ForceRecovery)
271 .with_severity(ErrorSeverity::Error)
272 }
273
274 pub fn range_error(
276 message: impl Into<String>,
277 field_name: &str,
278 value: u64,
279 min_allowed: u64,
280 max_allowed: u64,
281 ) -> RecoveryError {
282 RecoveryError::new(RecoveryErrorKind::Validation, message)
283 .with_context(ValidationErrorContext::range_validation(
284 field_name,
285 value,
286 min_allowed,
287 max_allowed,
288 ))
289 .with_recovery(RecoverySuggestion::ForceRecovery)
290 .with_severity(ErrorSeverity::Error)
291 }
292
293 pub fn dependency_error(
295 message: impl Into<String>,
296 dependent_type: &str,
297 dependency_type: &str,
298 dependency_id: u64,
299 ) -> RecoveryError {
300 RecoveryError::new(RecoveryErrorKind::Consistency, message)
301 .with_context(ValidationErrorContext::dependency_validation(
302 dependent_type,
303 dependency_type,
304 dependency_id,
305 true,
306 ))
307 .with_recovery(RecoverySuggestion::ForceRecovery)
308 .with_severity(ErrorSeverity::Error)
309 }
310
311 pub fn integrity_error(
313 message: impl Into<String>,
314 lsn_range: Option<(u64, u64)>,
315 affected_records: u64,
316 ) -> RecoveryError {
317 let mut context =
318 ValidationErrorContext::v2_format(lsn_range, "V2 Integrity", Some("Corrupted"));
319 context
320 .metadata
321 .insert("affected_records".to_string(), affected_records.to_string());
322
323 RecoveryError::new(RecoveryErrorKind::Corruption, message)
324 .with_context(context)
325 .with_recovery(RecoverySuggestion::RestoreFromBackup)
326 .with_severity(ErrorSeverity::Critical)
327 }
328
329 pub fn schema_error(
331 message: impl Into<String>,
332 expected_version: &str,
333 actual_version: &str,
334 ) -> RecoveryError {
335 let mut context =
336 ValidationErrorContext::v2_format(None, expected_version, Some(actual_version));
337 context.recovery_state = Some("Schema Validation".to_string());
338
339 RecoveryError::new(RecoveryErrorKind::V2Integration, message)
340 .with_context(context)
341 .with_recovery(RecoverySuggestion::ValidateWalFile)
342 .with_severity(ErrorSeverity::Critical)
343 }
344}
345
346pub trait ValidationErrorExt {
348 fn as_validation_error(self, validation_stage: &str) -> Self;
350
351 fn with_validation_context(
353 self,
354 stage: &str,
355 record_count: Option<u64>,
356 failure_count: Option<u64>,
357 ) -> Self;
358
359 fn as_recoverable_validation(self) -> Self;
361
362 fn as_critical_validation(self) -> Self;
364}
365
366impl ValidationErrorExt for RecoveryError {
367 fn as_validation_error(self, validation_stage: &str) -> Self {
368 let mut context = self.context.clone();
369 context.recovery_state = Some(format!("Validation: {}", validation_stage));
370
371 self.with_context(context)
372 .with_kind(RecoveryErrorKind::Validation)
373 }
374
375 fn with_validation_context(
376 self,
377 stage: &str,
378 record_count: Option<u64>,
379 failure_count: Option<u64>,
380 ) -> Self {
381 let mut context = self.context.clone();
382 context.recovery_state = Some(format!("Validation: {}", stage));
383
384 if let Some(count) = record_count {
385 context
386 .metadata
387 .insert("records_validated".to_string(), count.to_string());
388 }
389
390 if let Some(count) = failure_count {
391 context
392 .metadata
393 .insert("validation_failures".to_string(), count.to_string());
394 }
395
396 self.with_context(context)
397 }
398
399 fn as_recoverable_validation(self) -> Self {
400 self.with_severity(ErrorSeverity::Error)
401 .with_recovery(RecoverySuggestion::ForceRecovery)
402 }
403
404 fn as_critical_validation(self) -> Self {
405 self.with_severity(ErrorSeverity::Critical)
406 .with_recovery(RecoverySuggestion::RestoreFromBackup)
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::super::core::ErrorSeverity;
413 use super::*;
414
415 #[test]
416 fn test_validation_error_context_v2_format() {
417 let context = ValidationErrorContext::v2_format(Some((1000, 2000)), "V2.0", Some("V1.0"));
418
419 assert_eq!(context.lsn_range, Some((1000, 2000)));
420 assert_eq!(
421 context.recovery_state,
422 Some("V2 Format Validation".to_string())
423 );
424 assert_eq!(
425 context.metadata.get("expected_format"),
426 Some(&"V2.0".to_string())
427 );
428 assert_eq!(
429 context.metadata.get("actual_format"),
430 Some(&"V1.0".to_string())
431 );
432 }
433
434 #[test]
435 fn test_validation_error_context_checksum() {
436 let context = ValidationErrorContext::checksum_validation(1234, 0xABCD, 0xDCBA, "CRC32");
437
438 assert_eq!(context.lsn_range, Some((1234, 1234)));
439 assert_eq!(
440 context.recovery_state,
441 Some("Checksum Validation".to_string())
442 );
443 assert_eq!(context.metadata.get("lsn"), Some(&"1234".to_string()));
444 assert_eq!(
445 context.metadata.get("expected_checksum"),
446 Some(&"abcd".to_string())
447 );
448 assert_eq!(
449 context.metadata.get("actual_checksum"),
450 Some(&"dcba".to_string())
451 );
452 assert_eq!(
453 context.metadata.get("checksum_algorithm"),
454 Some(&"CRC32".to_string())
455 );
456 }
457
458 #[test]
459 fn test_validation_error_factory_checksum() {
460 let error = ValidationErrorFactory::checksum_error(1234, 0xABCD, 0xDCBA, "CRC32");
461
462 assert_eq!(error.kind, RecoveryErrorKind::Validation);
463 assert!(error.message.contains("1234"));
464 assert!(error.message.contains("abcd"));
465 assert!(error.message.contains("dcba"));
466 assert!(matches!(error.recovery, RecoverySuggestion::ForceRecovery));
467 assert_eq!(error.severity(), ErrorSeverity::Error);
468 }
469
470 #[test]
471 fn test_validation_error_factory_consistency() {
472 let error = ValidationErrorFactory::consistency_error(
473 "Node count mismatch",
474 Some((100, 90)),
475 Some((200, 180)),
476 );
477
478 assert_eq!(error.kind, RecoveryErrorKind::Consistency);
479 assert_eq!(error.severity(), ErrorSeverity::Critical);
480 assert_eq!(
481 error.context.metadata.get("expected_node_count"),
482 Some(&"100".to_string())
483 );
484 assert_eq!(
485 error.context.metadata.get("actual_node_count"),
486 Some(&"90".to_string())
487 );
488 assert_eq!(
489 error.context.metadata.get("expected_edge_count"),
490 Some(&"200".to_string())
491 );
492 assert_eq!(
493 error.context.metadata.get("actual_edge_count"),
494 Some(&"180".to_string())
495 );
496 }
497
498 #[test]
499 fn test_validation_error_factory_structural() {
500 let error = ValidationErrorFactory::structural_error(
501 "Invalid field type",
502 "NodeRecord",
503 "node_id",
504 "u64",
505 );
506
507 assert_eq!(error.kind, RecoveryErrorKind::Validation);
508 assert_eq!(
509 error.context.metadata.get("record_type"),
510 Some(&"NodeRecord".to_string())
511 );
512 assert_eq!(
513 error.context.metadata.get("field_name"),
514 Some(&"node_id".to_string())
515 );
516 assert_eq!(
517 error.context.metadata.get("expected_type"),
518 Some(&"u64".to_string())
519 );
520 }
521
522 #[test]
523 fn test_validation_error_extension() {
524 let base_error = RecoveryError::new(RecoveryErrorKind::Io, "Test error");
525
526 let validation_error = base_error
527 .as_validation_error("Format Check")
528 .with_validation_context("V2", Some(100), Some(5));
529
530 assert_eq!(
531 validation_error.context.recovery_state,
532 Some("Validation: V2".to_string())
533 );
534 assert_eq!(
535 validation_error.context.metadata.get("records_validated"),
536 Some(&"100".to_string())
537 );
538 assert_eq!(
539 validation_error.context.metadata.get("validation_failures"),
540 Some(&"5".to_string())
541 );
542 }
543
544 #[test]
545 fn test_validation_error_recovery_levels() {
546 let base_error = RecoveryError::new(RecoveryErrorKind::Validation, "Test error");
547
548 let recoverable = base_error.clone().as_recoverable_validation();
549 let critical = base_error.as_critical_validation();
550
551 assert_eq!(recoverable.severity(), ErrorSeverity::Error);
552 assert!(matches!(
553 recoverable.recovery,
554 RecoverySuggestion::ForceRecovery
555 ));
556
557 assert_eq!(critical.severity(), ErrorSeverity::Critical);
558 assert!(matches!(
559 critical.recovery,
560 RecoverySuggestion::RestoreFromBackup
561 ));
562 }
563
564 #[test]
565 fn test_validation_error_factory_schema() {
566 let error = ValidationErrorFactory::schema_error("Version mismatch", "2.0", "1.0");
567
568 assert_eq!(error.kind, RecoveryErrorKind::V2Integration);
569 assert_eq!(error.severity(), ErrorSeverity::Critical);
570 assert_eq!(
571 error.context.recovery_state,
572 Some("Schema Validation".to_string())
573 );
574 assert_eq!(
575 error.context.metadata.get("expected_format"),
576 Some(&"2.0".to_string())
577 );
578 assert_eq!(
579 error.context.metadata.get("actual_format"),
580 Some(&"1.0".to_string())
581 );
582 }
583}