fix: reject scalar context foreach sources
This commit is contained in:
@@ -115,11 +115,7 @@ class SchemaView:
|
||||
"""Return the first declared homogeneous item schema for an array."""
|
||||
for cursor in self._cursors:
|
||||
for branch, _ in _structural_branches(cursor, frozenset()):
|
||||
schema_type = branch.contents.get("type")
|
||||
is_array = schema_type == "array" or (
|
||||
isinstance(schema_type, list) and "array" in schema_type
|
||||
)
|
||||
if not is_array:
|
||||
if not _declares_array(branch.contents):
|
||||
continue
|
||||
child = branch.child(branch.contents.get("items"))
|
||||
if child is not None:
|
||||
@@ -127,8 +123,16 @@ class SchemaView:
|
||||
return None
|
||||
|
||||
def is_array(self) -> bool:
|
||||
"""Return whether any structural branch declares an array."""
|
||||
return self.array_items() is not None
|
||||
"""Return whether any structural branch declares an array type.
|
||||
|
||||
JSON Schema permits arrays without an ``items`` keyword; those are
|
||||
still arrays whose element schema is unconstrained.
|
||||
"""
|
||||
return any(
|
||||
_declares_array(branch.contents)
|
||||
for cursor in self._cursors
|
||||
for branch, _ in _structural_branches(cursor, frozenset())
|
||||
)
|
||||
|
||||
def standalone_schema(self) -> dict[str, Any]:
|
||||
"""Detach this view as a valid Draft 2020-12 schema resource.
|
||||
@@ -208,6 +212,13 @@ class SchemaNavigator:
|
||||
return None, ""
|
||||
|
||||
|
||||
def _declares_array(schema: JsonSchema) -> bool:
|
||||
schema_type = schema.get("type")
|
||||
return schema_type == "array" or (
|
||||
isinstance(schema_type, list) and "array" in schema_type
|
||||
)
|
||||
|
||||
|
||||
def _structural_branches(
|
||||
cursor: _Cursor,
|
||||
active_references: frozenset[ReferenceIdentity],
|
||||
|
||||
@@ -79,7 +79,7 @@ def validate_context_paths(
|
||||
# Context-rooted `over` paths reach this pass; the old
|
||||
# input/state-only check stays permissive for them.
|
||||
if node.over.root == "context":
|
||||
_validate_one_context_path(
|
||||
_validate_context_foreach_source(
|
||||
node.over, f"nodes[{idx}].over", node.id, schema, report
|
||||
)
|
||||
elif isinstance(node, InterruptNode):
|
||||
@@ -197,6 +197,30 @@ def _validate_one_context_path(
|
||||
)
|
||||
|
||||
|
||||
def _validate_context_foreach_source(
|
||||
path: GraphSourcePath,
|
||||
location: str,
|
||||
node_id: str,
|
||||
schema: ContextSchema | None,
|
||||
report: ValidationReport,
|
||||
) -> None:
|
||||
"""Require an existing context path to declare an array source.
|
||||
|
||||
Missing paths keep the more precise ``INVALID_CONTEXT_PATH`` diagnostic;
|
||||
this adds the foreach-specific error only after the path resolves.
|
||||
"""
|
||||
_validate_one_context_path(path, location, node_id, schema, report)
|
||||
if schema is None:
|
||||
return
|
||||
source = SchemaNavigator(schema).at_path(path.parts)
|
||||
if source is not None and not source.is_array():
|
||||
report.add(
|
||||
ValidationIssueCode.INVALID_FOREACH_SOURCE,
|
||||
location,
|
||||
f"foreach source {str(path)!r} must resolve to an array",
|
||||
)
|
||||
|
||||
|
||||
def _failing_segment(
|
||||
schema: Mapping[str, Any], parts: tuple[str, ...]
|
||||
) -> tuple[str | None, str]:
|
||||
|
||||
Reference in New Issue
Block a user