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