""" Validation Error Models for Issue #8: Get Validation Errors. This module provides structured error reporting for schema validation failures, enabling detailed feedback for arc42 compliance checking and user guidance. """ from typing import List, Dict, Any, Optional from dataclasses import dataclass from enum import Enum class ValidationErrorType(Enum): """Types of validation errors that can occur during schema validation.""" MISSING_REQUIRED_HEADING = "missing_required_heading" UNEXPECTED_HEADING = "unexpected_heading" INSUFFICIENT_CONTENT = "insufficient_content" EXCESS_CONTENT = "excess_content" MISSING_REQUIRED_SECTION = "missing_required_section" HEADING_COUNT_MISMATCH = "heading_count_mismatch" CONTENT_COUNT_MISMATCH = "content_count_mismatch" STRUCTURAL_VIOLATION = "structural_violation" @dataclass class ValidationError: """ Represents a specific validation error with detailed information. This class provides structured error information that can be used to generate user-friendly error messages and guide compliance fixing. """ error_type: ValidationErrorType message: str path: str # JSON path or section identifier expected: Optional[Any] = None actual: Optional[Any] = None suggestion: Optional[str] = None severity: str = "error" # error, warning, info def __str__(self) -> str: """Return a formatted error message for display.""" return self.message class ValidationErrorCollector: """ Collects and manages validation errors during schema validation. This class accumulates validation errors and provides methods to format and present them in user-friendly ways. """ def __init__(self): """Initialize empty error collector.""" self.errors: List[ValidationError] = [] def add_error( self, error_type: ValidationErrorType, message: str, path: str, expected: Optional[Any] = None, actual: Optional[Any] = None, suggestion: Optional[str] = None, severity: str = "error" ) -> None: """Add a validation error to the collection.""" error = ValidationError( error_type=error_type, message=message, path=path, expected=expected, actual=actual, suggestion=suggestion, severity=severity ) self.errors.append(error) def has_errors(self) -> bool: """Check if any errors have been collected.""" return len(self.errors) > 0 def get_error_count(self) -> int: """Get the total number of errors.""" return len(self.errors) def get_errors_by_type(self, error_type: ValidationErrorType) -> List[ValidationError]: """Get all errors of a specific type.""" return [error for error in self.errors if error.error_type == error_type] def get_errors_by_severity(self, severity: str) -> List[ValidationError]: """Get all errors of a specific severity level.""" return [error for error in self.errors if error.severity == severity] def format_errors(self, format_type: str = "text") -> str: """ Format all errors for display. Args: format_type: Output format ('text', 'json', 'markdown') Returns: Formatted error string """ if not self.errors: return "No validation errors found." if format_type == "json": import json error_data = [ { "type": error.error_type.value, "message": error.message, "path": error.path, "expected": error.expected, "actual": error.actual, "suggestion": error.suggestion, "severity": error.severity } for error in self.errors ] return json.dumps(error_data, indent=2) elif format_type == "markdown": lines = ["# Validation Errors\n"] # Group errors by severity error_groups = {} for error in self.errors: if error.severity not in error_groups: error_groups[error.severity] = [] error_groups[error.severity].append(error) for severity in ["error", "warning", "info"]: if severity in error_groups: lines.append(f"## {severity.title()}s\n") for error in error_groups[severity]: lines.append(f"- **{error.path}**: {error.message}") if error.expected is not None: lines.append(f" - Expected: {error.expected}") if error.actual is not None: lines.append(f" - Actual: {error.actual}") if error.suggestion: lines.append(f" - Suggestion: {error.suggestion}") lines.append("") return "\n".join(lines) else: # text format lines = [] lines.append(f"Validation failed with {len(self.errors)} error(s):") lines.append("") for i, error in enumerate(self.errors, 1): lines.append(f"{i}. [{error.severity.upper()}] {error.path}") lines.append(f" {error.message}") if error.expected is not None: lines.append(f" Expected: {error.expected}") if error.actual is not None: lines.append(f" Actual: {error.actual}") if error.suggestion: lines.append(f" 💡 {error.suggestion}") lines.append("") return "\n".join(lines) def get_summary(self) -> Dict[str, int]: """Get a summary of error counts by type and severity.""" summary = { "total_errors": len(self.errors), "by_severity": {}, "by_type": {} } for error in self.errors: # Count by severity if error.severity not in summary["by_severity"]: summary["by_severity"][error.severity] = 0 summary["by_severity"][error.severity] += 1 # Count by type error_type_name = error.error_type.value if error_type_name not in summary["by_type"]: summary["by_type"][error_type_name] = 0 summary["by_type"][error_type_name] += 1 return summary