markitect-main/markitect/validation_error.py

191 lines
6.5 KiB
Python
Raw Normal View History

"""
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