Source code for design_research_agents._schemas._validation

"""Validation helpers backed by JSON Schema Draft 2020-12."""

from __future__ import annotations

from collections.abc import Iterable, Mapping

from jsonschema import Draft202012Validator, SchemaError


[docs] class SchemaValidationError(ValueError): """Raised when a payload fails JSON Schema validation."""
[docs] def validate_payload_against_schema( *, payload: object, schema: Mapping[str, object] | None, location: str = "$", ) -> None: """Validate a payload against a JSON Schema document. Args: payload: Arbitrary payload value to validate. schema: Schema mapping to validate against. location: Location label used for error messages. Raises: SchemaValidationError: If validation fails. """ if schema is None: return if not isinstance(schema, Mapping): raise SchemaValidationError(f"{location}: schema must be a mapping") try: Draft202012Validator.check_schema(schema) validator = Draft202012Validator(schema) errors = sorted(validator.iter_errors(payload), key=lambda err: list(err.path)) except SchemaError as exc: raise SchemaValidationError(f"{location}: invalid JSON schema: {exc.message}") from exc if not errors: return raise SchemaValidationError(_format_validation_error(errors[0], location=location))
def _format_validation_error(error: object, *, location: str) -> str: path = getattr(error, "path", ()) validator = getattr(error, "validator", "") message = str(getattr(error, "message", error)) resolved_location = _join_location(location, path) if validator == "additionalProperties": return f"{resolved_location}: unexpected field ({message})" if validator == "anyOf": return f"{resolved_location}: payload did not satisfy anyOf constraints ({message})" if validator == "enum": instance = getattr(error, "instance", None) enum_values = getattr(error, "validator_value", ()) return f"{resolved_location}: value {instance!r} is not in enum {list(enum_values)!r}" if validator == "required": return f"{resolved_location}: required field missing ({message})" if validator == "type": expected = getattr(error, "validator_value", None) if isinstance(expected, str): return f"{resolved_location}: expected {expected}" return f"{resolved_location}: type mismatch ({message})" return f"{resolved_location}: {message}" def _join_location(base: str, path: Iterable[object]) -> str: location = base for part in path: if isinstance(part, int): location += f"[{part}]" else: location += f".{part}" return location