mirror of
https://github.com/Canner/WrenAI.git
synced 2026-09-01 04:57:29 +08:00
fix(context): harden description checks for bad MDL rows (#2616)
This commit is contained in:
@@ -1117,6 +1117,29 @@ def validate_project(project_path: Path) -> list[ValidationError]:
|
||||
)
|
||||
)
|
||||
|
||||
props = model.get("properties")
|
||||
if props is not None and not isinstance(props, dict):
|
||||
errors.append(
|
||||
ValidationError(
|
||||
"error",
|
||||
f"{src_path} > {name}",
|
||||
"properties must be a mapping",
|
||||
)
|
||||
)
|
||||
for j, col in enumerate(columns):
|
||||
if not isinstance(col, dict):
|
||||
continue
|
||||
col_props = col.get("properties")
|
||||
if col_props is not None and not isinstance(col_props, dict):
|
||||
col_name = col.get("name") or f"columns[{j}]"
|
||||
errors.append(
|
||||
ValidationError(
|
||||
"error",
|
||||
f"{src_path} > {name} > {col_name}",
|
||||
"properties must be a mapping",
|
||||
)
|
||||
)
|
||||
|
||||
# v1 legacy views.yml may contain non-mapping entries (e.g. `- null`).
|
||||
# load_views() silently drops those (matching the other loaders), but
|
||||
# validate_project's job is to tell the user about hand-edited mistakes
|
||||
@@ -1232,6 +1255,16 @@ def validate_project(project_path: Path) -> list[ValidationError]:
|
||||
)
|
||||
)
|
||||
|
||||
view_props = view.get("properties")
|
||||
if view_props is not None and not isinstance(view_props, dict):
|
||||
errors.append(
|
||||
ValidationError(
|
||||
"error",
|
||||
f"views/{src_dir}",
|
||||
"properties must be a mapping",
|
||||
)
|
||||
)
|
||||
|
||||
# Check relationships — walk the raw list when available so indices match
|
||||
# the file (filtered loader positions would renumber past dropped junk).
|
||||
all_entity_names = model_names | view_names
|
||||
@@ -1644,7 +1677,10 @@ _VALID_LEVELS = frozenset({"error", "warning", "strict"})
|
||||
|
||||
|
||||
def _prop_description(item: dict) -> str | None:
|
||||
return (item.get("properties") or {}).get("description")
|
||||
props = item.get("properties") or {}
|
||||
if not isinstance(props, dict):
|
||||
return None
|
||||
return props.get("description")
|
||||
|
||||
|
||||
def _check_descriptions(manifest: dict, *, strict: bool = False) -> list[str]:
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Guards for non-mapping properties and safe description lookup."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from wren.context import _prop_description, validate_project
|
||||
|
||||
|
||||
def test_prop_description_non_mapping_returns_none():
|
||||
assert _prop_description({"properties": "oops"}) is None
|
||||
assert _prop_description({"properties": {"description": "ok"}}) == "ok"
|
||||
assert _prop_description({}) is None
|
||||
|
||||
|
||||
def test_validate_project_reports_non_mapping_properties(tmp_path: Path):
|
||||
(tmp_path / "wren_project.yml").write_text(
|
||||
"name: demo\ndata_source: postgres\nschema_version: 2\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
model_dir = tmp_path / "models" / "orders"
|
||||
model_dir.mkdir(parents=True)
|
||||
(model_dir / "metadata.yml").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
name: orders
|
||||
table_reference:
|
||||
table: orders
|
||||
properties: oops
|
||||
columns:
|
||||
- name: id
|
||||
type: INTEGER
|
||||
properties: not-a-map
|
||||
"""
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
view_dir = tmp_path / "views" / "v1"
|
||||
view_dir.mkdir(parents=True)
|
||||
(view_dir / "metadata.yml").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
name: v1
|
||||
properties: []
|
||||
"""
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(view_dir / "sql.yml").write_text(
|
||||
"statement: SELECT 1\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
errors = validate_project(tmp_path)
|
||||
messages = [f"{e.path}: {e.message}" for e in errors]
|
||||
joined = "\n".join(messages)
|
||||
assert "properties must be a mapping" in joined
|
||||
# model + column + view
|
||||
assert sum("properties must be a mapping" in m for m in messages) == 3
|
||||
Reference in New Issue
Block a user