From bab4f91da7dade535c36b14336991642199926c1 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sat, 31 Jan 2026 07:10:13 +0000 Subject: [PATCH] Initial upload: ConfDoc v0.1.0 - Config validation and documentation generator --- src/confdoc/validator/validator.py | 127 +++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/confdoc/validator/validator.py diff --git a/src/confdoc/validator/validator.py b/src/confdoc/validator/validator.py new file mode 100644 index 0000000..0ad8add --- /dev/null +++ b/src/confdoc/validator/validator.py @@ -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)