deerflow-code/offline-backend-20260512/backend/packages/harness/deerflow/workflows/schema_validation.py
2026-09-07 18:24:55 +08:00

74 lines
3.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Full JSON Schema validation for workflow input/output values.
The engine used to check only ``required`` keys; everything else (wrong type,
unknown enum value, nested ``properties``/``items`` violations) slipped through
until downstream nodes exploded with confusing errors. This module runs the
graph's ``inputSchema`` / ``outputSchema`` through ``Draft202012Validator`` and
returns structured, frontend-friendly errors (path + message) instead of
leaking Python tracebacks.
Kept as pure functions so both the node executors (``WorkflowError``) and the
HTTP router (400 responses) can share the exact same rules.
"""
from __future__ import annotations
from typing import Any
from jsonschema import Draft202012Validator
# Cap the reported errors so a wildly invalid payload cannot produce a
# multi-megabyte error body.
_MAX_ERRORS = 20
def schema_issues(value: Any, schema: dict[str, Any] | None) -> list[dict[str, Any]]:
"""Validate ``value`` against ``schema``.
Returns ``[]`` when the schema is empty/absent (nothing declared = nothing
enforced). Each issue is ``{"path": [...], "message": str}`` with ``path``
pointing at the offending field, e.g. ``["options", 0, "name"]``.
"""
if not isinstance(schema, dict) or not schema:
return []
try:
validator = Draft202012Validator(schema)
# Construction alone does not check the schema — malformed keywords
# (e.g. a non-string ``pattern``) only explode during validation.
errors = sorted(validator.iter_errors(value), key=lambda err: list(err.absolute_path))
except Exception: # noqa: BLE001 - a malformed user schema must not 500
return [{"path": [], "message": "schema 定义本身无效,无法用于校验"}]
out: list[dict[str, Any]] = []
seen: set[tuple[Any, ...]] = set()
for err in errors:
path = [str(p) if not isinstance(p, int) else p for p in err.absolute_path]
key = tuple(path)
if key in seen:
continue
seen.add(key)
out.append({"path": path, "message": _humanize(err)})
if len(out) >= _MAX_ERRORS:
out.append({"path": [], "message": f"……其余问题已省略(共 {len(errors)} 处)"})
break
return out
def _humanize(err: Any) -> str:
"""Map the common jsonschema phrasings to short Chinese messages."""
message = str(err.message)
validator = getattr(err, "validator", "")
path = ".".join(str(p) for p in err.absolute_path) or "输入"
if validator == "required":
missing = message.split("'")[1] if "'" in message else "?"
return f"{path} 缺少必填字段:{missing}"
if validator == "enum":
return f"{path} 的值不在允许范围内:{message}"
if validator == "type":
return f"{path} 类型不正确:{message}"
if validator == "additionalProperties":
return f"{path} 含有未声明的额外字段:{message}"
return f"{path} 不符合 schema:{message}"
__all__ = ["schema_issues"]