Compare commits

...
Author SHA1 Message Date
Elizabeth ThompsonandClaude Opus 4.8 c18c91a89d fix(chart): catch JSONDecodeError when parsing params on chart create
CreateChartCommand.__init__ called json.loads on the client-supplied
params string without guarding it. A malformed params value in
POST /api/v1/chart raised a raw JSONDecodeError out of __init__ --
before run()'s @transaction or validate() ran -- which the api.py
create() handler does not catch, surfacing as an opaque 500.

Wrap the parse and raise ChartInvalidError(exceptions=[...]) instead,
following the existing *ValidationError idiom, so the existing
except ChartInvalidError branch returns a 422. Adds a unit test
covering both the invalid-JSON and valid-JSON (happy path) cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:40:26 +00:00
3 changed files with 75 additions and 1 deletions
+7 -1
View File
@@ -29,6 +29,7 @@ from superset.commands.chart.exceptions import (
ChartCreateFailedError,
ChartForbiddenError,
ChartInvalidError,
ChartParamsInvalidJSONValidationError,
DashboardsForbiddenError,
DashboardsNotFoundValidationError,
)
@@ -47,7 +48,12 @@ class CreateChartCommand(CreateMixin, BaseCommand):
self._properties = data.copy()
if params_str := self._properties.get("params"):
params = json.loads(params_str)
try:
params = json.loads(params_str)
except json.JSONDecodeError as ex:
raise ChartInvalidError(
exceptions=[ChartParamsInvalidJSONValidationError()]
) from ex
if isinstance(params, dict) and "viz_type" in params:
# Only fall back to params when no top-level viz_type was supplied;
# an explicit top-level field takes precedence.
+9
View File
@@ -116,6 +116,15 @@ class ChartQueryContextDatasourceMismatchValidationError(ValidationError):
)
class ChartParamsInvalidJSONValidationError(ValidationError):
"""
Raised when the params field is not valid JSON.
"""
def __init__(self) -> None:
super().__init__(_("Chart params contains invalid JSON"), field_name="params")
class ChartNotFoundError(CommandException):
message = "Chart not found."
@@ -0,0 +1,59 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import pytest
from superset.commands.chart.create import CreateChartCommand
from superset.commands.chart.exceptions import (
ChartInvalidError,
ChartParamsInvalidJSONValidationError,
)
def test_init_with_invalid_json_params_raises_chart_invalid_error():
"""
A malformed ``params`` JSON string must surface as a ``ChartInvalidError``
(a 422-mapped validation error) rather than leaking a raw ``JSONDecodeError``.
"""
with pytest.raises(ChartInvalidError) as ex:
CreateChartCommand(
{
"params": "{not valid json",
"datasource_id": 1,
"datasource_type": "table",
}
)
assert any(
isinstance(exc, ChartParamsInvalidJSONValidationError)
for exc in ex.value._exceptions
)
def test_init_with_valid_json_params_populates_viz_type():
"""
A valid ``params`` JSON string still falls back to its ``viz_type`` when no
top-level ``viz_type`` is supplied (happy path must not regress).
"""
command = CreateChartCommand(
{
"params": '{"viz_type": "table"}',
"datasource_id": 1,
"datasource_type": "table",
}
)
assert command._properties["viz_type"] == "table"