Initial upload with full project structure
This commit is contained in:
101
app/src/confgen/validator.py
Normal file
101
app/src/confgen/validator.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
"""Schema validation for generated configurations."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaValidator:
|
||||||
|
"""JSON Schema validator for configuration validation."""
|
||||||
|
|
||||||
|
def __init__(self, schema_path: str):
|
||||||
|
self.schema_path = Path(schema_path)
|
||||||
|
self._schema = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self) -> dict[str, Any]:
|
||||||
|
"""Load and cache the schema."""
|
||||||
|
if self._schema is None:
|
||||||
|
with open(self.schema_path) as f:
|
||||||
|
import json
|
||||||
|
|
||||||
|
self._schema = json.load(f)
|
||||||
|
return self._schema
|
||||||
|
|
||||||
|
def validate(self, data: dict[str, Any]) -> tuple[bool, list[str]]:
|
||||||
|
"""Validate data against the schema."""
|
||||||
|
from jsonschema import Draft7Validator
|
||||||
|
|
||||||
|
validator = Draft7Validator(self.schema)
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for error in validator.iter_errors(data):
|
||||||
|
errors.append(self._format_error(error))
|
||||||
|
|
||||||
|
return len(errors) == 0, errors
|
||||||
|
|
||||||
|
def _format_error(self, error) -> str:
|
||||||
|
"""Format a validation error for display."""
|
||||||
|
path = " -> ".join(str(p) for p in error.absolute_path)
|
||||||
|
message = error.message
|
||||||
|
|
||||||
|
if error.validator == "type":
|
||||||
|
message = f"expected {error.validator_value}, got {type(error.instance).__name__}"
|
||||||
|
elif error.validator == "enum":
|
||||||
|
message = f"must be one of {error.validator_value}"
|
||||||
|
elif error.validator == "minimum":
|
||||||
|
message = f"minimum value is {error.validator_value}"
|
||||||
|
elif error.validator == "maximum":
|
||||||
|
message = f"maximum value is {error.validator_value}"
|
||||||
|
elif error.validator == "minLength":
|
||||||
|
message = f"must have at least {error.validator_value} characters"
|
||||||
|
elif error.validator == "maxLength":
|
||||||
|
message = f"must have at most {error.validator_value} characters"
|
||||||
|
elif error.validator == "pattern":
|
||||||
|
message = f"must match pattern: {error.validator_value}"
|
||||||
|
|
||||||
|
return f"[{path}] {message}"
|
||||||
|
|
||||||
|
def validate_file(self, config_path: str) -> tuple[bool, list[str]]:
|
||||||
|
"""Validate a configuration file against the schema."""
|
||||||
|
from .parsers import ConfigParser
|
||||||
|
|
||||||
|
parser = ConfigParser()
|
||||||
|
data = parser.load_file(config_path)
|
||||||
|
return self.validate(data)
|
||||||
|
|
||||||
|
def get_schema_summary(self) -> dict[str, Any]:
|
||||||
|
"""Get a summary of the schema."""
|
||||||
|
summary = {
|
||||||
|
"type": self.schema.get("type", "object"),
|
||||||
|
"required": self.schema.get("required", []),
|
||||||
|
"properties": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, prop in self.schema.get("properties", {}).items():
|
||||||
|
prop_info = {
|
||||||
|
"type": prop.get("type", "any"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if "enum" in prop:
|
||||||
|
prop_info["enum"] = prop["enum"]
|
||||||
|
if "default" in prop:
|
||||||
|
prop_info["default"] = prop["default"]
|
||||||
|
if "description" in prop:
|
||||||
|
prop_info["description"] = prop["description"]
|
||||||
|
|
||||||
|
summary["properties"][name] = prop_info
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
def check_property_exists(self, data: dict[str, Any], property: str) -> bool:
|
||||||
|
"""Check if a property exists in the data."""
|
||||||
|
keys = property.split(".")
|
||||||
|
current = data
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
if isinstance(current, dict) and key in current:
|
||||||
|
current = current[key]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
Reference in New Issue
Block a user