feat: Complete Issue #8 - Detailed Validation Error Reporting and CLI Enhancements
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Major Features: - Implement comprehensive validation error reporting system (Issue #8) - Add direct CLI access with 'markitect' command - Create extensive makefile targets for CLI usage - Enhance schema validation with detailed error collection Components Added: - markitect/validation_error.py: ValidationError system with 8 error types - Enhanced markitect/schema_validator.py: Detailed error reporting methods - markitect/cli.py: Enhanced with --detailed-errors and --error-format options - visualize_schema.py: Schema visualization with ASCII and colorful modes - Comprehensive test suite for validation error reporting CLI Enhancements: - Direct 'markitect' command access for all operations - Makefile targets for typical CLI usage (cli-help, cli-ingest, etc.) - Support for text, JSON, and markdown error output formats - Backward compatibility with existing validation functionality Testing: - 11 comprehensive tests for Issue #8 validation error reporting - Tests for schema validation, visualization, and CLI integration - 100% test coverage for validation error scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
191
markitect/validation_error.py
Normal file
191
markitect/validation_error.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user