Skip to content

Commit ebb3dd4

Browse files
EnxDevclaude
andcommitted
fix(mcp): broaden tagged-union cleanup and drop chart-type-specific hint
Addresses review feedback on #39522: 1. ``_sanitize_validation_error`` only stripped the Pydantic ``tagged-union[...]`` header when the body began with ``Value error,``. Other common Pydantic failure bodies — ``Input should be ...`` from literal enums, ``String should match pattern ...``, etc. — still kept the long header and got truncated before the actionable part. Match the first two-space-indented body line instead, which is Pydantic's universal separator between the tagged-union header and the per-field message. Works for every body style without a keyword allowlist. 2. The ``validation_error`` template fires for every chart type, so the ``primary_kind`` / ``secondary_kind`` suggestion was misleading for non-mixed_timeseries failures. Replace it with chart-type-agnostic guidance that points callers at the valid-fields list already rendered in the error details. Add regression tests for the non-``Value error`` body case (invalid aggregate enum on a pie chart) and for the suggestion's chart-type agnosticism. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2054487 commit ebb3dd4

3 files changed

Lines changed: 70 additions & 9 deletions

File tree

superset/mcp_service/chart/validation/pipeline.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,19 @@ def _sanitize_validation_error(error: Exception) -> str:
8181

8282
# Pydantic tagged-union errors prefix the message with a long
8383
# ``1 validation error for tagged-union[...]`` header before the
84-
# actionable ``Value error, ...`` body — extract the body so the
85-
# 200-char truncation below doesn't swallow the useful part.
86-
if "tagged-union[" in error_str and "Value error," in error_str:
87-
idx = error_str.find("Value error,")
88-
footer = error_str.find("\n For further information")
89-
end = footer if footer != -1 else len(error_str)
90-
error_str = error_str[idx:end].strip()
84+
# per-field body (e.g. ``Value error, ...``, ``Field required``,
85+
# ``Input should be ...``). The body always lives on a line indented
86+
# by exactly two spaces — pull it out so the 200-char truncation
87+
# below doesn't swallow the actionable part. The pydantic footer
88+
# ``\n For further information ...`` uses four-space indent and
89+
# is dropped here.
90+
if "tagged-union[" in error_str:
91+
body_match = re.search(r"\n (?! )", error_str)
92+
if body_match:
93+
idx = body_match.end()
94+
footer_idx = error_str.find("\n For further information", idx)
95+
end = footer_idx if footer_idx != -1 else len(error_str)
96+
error_str = error_str[idx:end].strip()
9197

9298
# SECURITY FIX: Limit length FIRST to prevent ReDoS attacks
9399
if len(error_str) > 200:

superset/mcp_service/utils/error_builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ class ChartErrorBuilder:
194194
"chart_type's schema",
195195
"Call get_chart_type_schema or read the chart://configs resource "
196196
"for valid fields and examples",
197-
"For mixed_timeseries charts, use 'primary_kind' and "
198-
"'secondary_kind' (not 'kind' / 'kind_secondary')",
197+
"Replace any unknown field names with the ones listed in the "
198+
"error details above",
199199
],
200200
},
201201
}

tests/unit_tests/mcp_service/chart/validation/test_pipeline_error_surface.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,58 @@ def test_valid_mixed_timeseries_config_passes() -> None:
138138

139139
assert result.is_valid is True
140140
assert result.error is None
141+
142+
143+
def test_non_value_error_pydantic_body_is_surfaced() -> None:
144+
"""The tagged-union cleanup must also handle non-``Value error`` bodies
145+
like ``Input should be ...`` (literal_error from Pydantic enums).
146+
147+
Regression for the case where the sanitizer only matched ``Value error,``
148+
and left the long tagged-union header attached, causing the 200-char
149+
truncation to swallow the actionable message.
150+
"""
151+
# Invalid aggregate value — triggers a Pydantic literal_error whose body
152+
# starts with ``Input should be 'SUM', 'COUNT', ...``, not ``Value error,``.
153+
request_data = {
154+
"dataset_id": 1,
155+
"config": {
156+
"chart_type": "pie",
157+
"dimension": {"name": "product"},
158+
"metric": {"name": "revenue", "aggregate": "BOGUS"},
159+
},
160+
}
161+
162+
with (
163+
patch.object(ValidationPipeline, "_get_dataset_context", return_value=None),
164+
patch.object(
165+
ValidationPipeline, "_validate_dataset", return_value=(True, None)
166+
),
167+
patch.object(
168+
ValidationPipeline, "_validate_runtime", return_value=(True, None)
169+
),
170+
patch.object(
171+
ValidationPipeline,
172+
"_normalize_column_names",
173+
side_effect=_passthrough_normalize,
174+
),
175+
):
176+
result = ValidationPipeline.validate_request_with_warnings(request_data)
177+
178+
assert result.is_valid is False
179+
assert result.error is not None
180+
dumped = result.error.model_dump()
181+
assert dumped["error_code"] == "VALIDATION_PIPELINE_ERROR"
182+
assert "tagged-union" not in dumped["message"]
183+
assert "tagged-union" not in dumped["details"]
184+
# The actionable body must survive the 200-char truncation.
185+
assert "Input should be" in dumped["details"]
186+
187+
188+
def test_validation_error_template_suggestions_are_chart_type_agnostic() -> None:
189+
"""The ``validation_error`` template fires for every chart type, so its
190+
suggestions must not mention specific chart-type field names."""
191+
template = ChartErrorBuilder.TEMPLATES["validation_error"]
192+
joined = " ".join(template["suggestions"]).lower()
193+
assert "mixed_timeseries" not in joined
194+
assert "primary_kind" not in joined
195+
assert "secondary_kind" not in joined

0 commit comments

Comments
 (0)