Files

114 lines
4.2 KiB
Python

"""Schema validation helpers for ComfyUI JSON nodes."""
from __future__ import annotations
import re
from typing import Any
from .constants import SUPPORTED_SCHEMA_TYPES
from .errors import push_error, truncate_repr
def type_matches(value: Any, expected: str) -> bool:
if expected == "string":
return isinstance(value, str)
if expected == "int":
return isinstance(value, int) and not isinstance(value, bool)
if expected == "float":
return isinstance(value, (int, float)) and not isinstance(value, bool)
if expected == "boolean":
return isinstance(value, bool)
if expected == "array":
return isinstance(value, list)
if expected == "object":
return isinstance(value, dict)
return expected == "any"
def validate_schema(value: Any, schema: dict[str, Any], path: str, strict: bool, errors: list[dict[str, str]]) -> None:
if not isinstance(schema, dict):
push_error(errors, path, "Schema node must be an object", "invalid_schema")
return
expected_type = schema.get("type", "any")
if expected_type not in SUPPORTED_SCHEMA_TYPES:
push_error(errors, path, f"Unsupported schema type '{expected_type}'", "invalid_schema")
return
if not type_matches(value, expected_type):
push_error(
errors,
path,
f"Expected type '{expected_type}', got '{type(value).__name__}' (value: {truncate_repr(value)})",
"type_mismatch",
)
return
if "enum" in schema:
enum_values = schema["enum"]
if not isinstance(enum_values, list):
push_error(errors, path, "`enum` must be an array", "invalid_schema")
return
if value not in enum_values:
push_error(
errors,
path,
f"Value {truncate_repr(value)} not in allowed values {truncate_repr(enum_values)}",
"enum_mismatch",
)
if expected_type == "string" and "pattern" in schema:
pattern = schema["pattern"]
if not isinstance(pattern, str):
push_error(errors, path, "`pattern` must be a string", "invalid_schema")
return
try:
matches = re.search(pattern, value) is not None
except re.error as exc:
push_error(errors, path, f"Invalid regex pattern '{pattern}': {exc}", "invalid_schema")
return
if not matches:
push_error(
errors,
path,
f"Value {truncate_repr(value)} does not match pattern '{pattern}'",
"pattern_mismatch",
)
if expected_type == "object":
properties = schema.get("properties", {})
required = schema.get("required", [])
if not isinstance(properties, dict):
push_error(errors, path, "`properties` must be an object", "invalid_schema")
return
if not isinstance(required, list) or not all(isinstance(item, str) for item in required):
push_error(errors, path, "`required` must be an array of strings", "invalid_schema")
return
for required_key in required:
if required_key not in value:
push_error(errors, f"{path}.{required_key}", "Required key is missing", "required_missing")
if strict:
for key in value:
if key not in properties:
push_error(errors, f"{path}.{key}", "Key is not allowed in strict mode", "unexpected_key")
for key, sub_schema in properties.items():
if not isinstance(sub_schema, dict):
push_error(errors, f"{path}.{key}", "Property schema must be an object", "invalid_schema")
continue
if key in value:
validate_schema(value[key], sub_schema, f"{path}.{key}", strict, errors)
elif expected_type == "array":
item_schema = schema.get("items")
if item_schema is None:
return
if not isinstance(item_schema, dict):
push_error(errors, path, "`items` must be an object", "invalid_schema")
return
for index, item in enumerate(value):
validate_schema(item, item_schema, f"{path}[{index}]", strict, errors)