Initial upload: ConfDoc v0.1.0 - Config validation and documentation generator

This commit is contained in:
2026-01-31 07:10:13 +00:00
parent 95403b399d
commit bab4f91da7

View File

@@ -0,0 +1,127 @@
from typing import Any, Dict, List, Optional
from jsonschema import validate, ValidationError as JsonSchemaValidationError
from jsonschema.exceptions import best_match
from confdoc.validator.errors import ValidationError as CustomValidationError, ErrorFormatter, ErrorSeverity
class SchemaValidator:
"""Validates configuration against JSON Schema with detailed error reporting."""
def __init__(self, draft: str = "draft-07"):
"""Initialize validator with schema draft."""
self.draft = draft
def validate(self, config: Dict[str, Any], schema: Dict[str, Any]) -> tuple:
"""
Validate configuration against schema.
Returns:
tuple: (is_valid: bool, errors: List[CustomValidationError])
"""
errors = []
try:
validate(instance=config, schema=schema, cls=_get_validator_class(self.draft))
except JsonSchemaValidationError as e:
errors = self._process_validation_error(e, config)
return (len(errors) == 0, errors)
def validate_multiple(self, configs: List[Dict[str, Any]], schema: Dict[str, Any]) -> Dict[int, List[CustomValidationError]]:
"""Validate multiple configurations and return errors for each."""
results = {}
for i, config in enumerate(configs):
is_valid, errors = self.validate(config, schema)
if not is_valid:
results[i] = errors
return results
def _process_validation_error(
self,
error: JsonSchemaValidationError,
config: Dict[str, Any]
) -> List[CustomValidationError]:
"""Process jsonschema ValidationError into CustomValidationError objects."""
errors = []
for schema_error in error.context:
error_info = self._extract_error_info(schema_error)
severity = ErrorFormatter.classify_severity(error_info)
suggestion = ErrorFormatter.generate_suggestion(error_info)
custom_error = CustomValidationError(
message=schema_error.message,
path=self._format_path(schema_error.json_path),
line=error_info.get("line"),
column=error_info.get("column"),
severity=severity,
suggestion=suggestion,
validator=error_info.get("validator"),
)
errors.append(custom_error)
if not errors:
errors = [
CustomValidationError(
message=error.message,
path=self._format_path(error.json_path),
severity=ErrorSeverity.ERROR,
)
]
return errors
def _extract_error_info(self, error: JsonSchemaValidationError) -> Dict[str, Any]:
"""Extract detailed information from a validation error."""
info = {
"validator": getattr(error, "validator", None),
"validator_value": getattr(error, "validator_value", None),
"message": error.message,
}
if hasattr(error, "absolute_schema_path"):
schema_path = list(error.absolute_schema_path)
if schema_path:
info["validator"] = schema_path[-1] if isinstance(schema_path[-1], str) else info["validator"]
return info
def _format_path(self, json_path: str) -> str:
"""Format JSON path to dot notation."""
if not json_path or json_path == "$":
return ""
path = json_path.replace("$.", "").replace(".", ".")
return path
def get_best_match(self, config: Dict[str, Any], schema: Dict[str, Any]) -> Optional[CustomValidationError]:
"""Get the most relevant validation error."""
try:
validate(instance=config, schema=schema, cls=_get_validator_class(self.draft))
return None
except JsonSchemaValidationError as e:
best = best_match(e.context)
if best:
return CustomValidationError(
message=best.message,
path=self._format_path(best.json_path),
severity=ErrorFormatter.classify_severity(
{"validator": getattr(best, "validator", None)}
),
)
return CustomValidationError(message=e.message)
def _get_validator_class(draft: str):
"""Get the appropriate validator class for the draft."""
from jsonschema import Draft7Validator, Draft201909Validator
validators = {
"draft-07": Draft7Validator,
"2019-09": Draft201909Validator,
}
return validators.get(draft, Draft7Validator)