From bdf9583ada4392991af3a570b32ff2f02c66db21 Mon Sep 17 00:00:00 2001 From: Ihor Yeromin Date: Tue, 2 Sep 2025 20:00:14 +0200 Subject: [PATCH] SSE: Return error messages instead of 500 on SSE command parse errors (#109480) fixes #108897 --------- Co-authored-by: Kyle Brandt --- pkg/expr/commands.go | 2 +- pkg/expr/errors.go | 19 +++++++++++++++++++ pkg/expr/nodes.go | 2 +- pkg/expr/service_test.go | 19 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/pkg/expr/commands.go b/pkg/expr/commands.go index 5b18ce1a104..8883a8c3fda 100644 --- a/pkg/expr/commands.go +++ b/pkg/expr/commands.go @@ -58,7 +58,7 @@ func UnmarshalMathCommand(rn *rawNode) (*MathCommand, error) { gm, err := NewMathCommand(rn.RefID, exprString) if err != nil { - return nil, fmt.Errorf("invalid math command type: %w", err) + return nil, fmt.Errorf("invalid math command: %w", err) } return gm, nil } diff --git a/pkg/expr/errors.go b/pkg/expr/errors.go index 072a2d27f4f..f2800342e37 100644 --- a/pkg/expr/errors.go +++ b/pkg/expr/errors.go @@ -75,6 +75,25 @@ func MakeDependencyError(refID, depRefID string) error { return DependencyError.Build(data) } +var parsErrStr = "failed to parse expression [{{ .Public.refId }}]: {{.Public.error}}" + +var ParseError = errutil.NewBase( + errutil.StatusBadRequest, "sse.parseError").MustTemplate( + parsErrStr, + errutil.WithPublic(parsErrStr)) + +func MakeParseError(refID string, err error) error { + data := errutil.TemplateData{ + Public: map[string]interface{}{ + "refId": refID, + "error": err.Error(), + }, + Error: err, + } + + return ParseError.Build(data) +} + var unexpectedNodeTypeErrString = "expected executable node type but got node type [{{ .Public.nodeType }} for refid [{{ .Public.refId}}]" var UnexpectedNodeTypeError = errutil.NewBase( diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 8e65ff9858b..2aa4d26ad53 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -154,7 +154,7 @@ func buildCMDNode(ctx context.Context, rn *rawNode, toggles featuremgmt.FeatureT return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID) } if err != nil { - return nil, fmt.Errorf("failed to parse expression '%v': %w", rn.RefID, err) + return nil, MakeParseError(rn.RefID, err) } return node, nil diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 42656f55a31..0fb46e18ddf 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -148,6 +148,25 @@ func TestDSQueryError(t *testing.T) { require.Equal(t, fp(42), res.Responses["C"].Frames[0].Fields[0].At(0)) } +func TestParseError(t *testing.T) { + resp := map[string]backend.DataResponse{} + + queries := []Query{ + { + RefID: "A", + DataSource: dataSourceModel(), + JSON: json.RawMessage(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "math", "expression": "asdf" }`), + }, + } + + s, req := newMockQueryService(resp, queries) + + _, err := s.BuildPipeline(t.Context(), req) + require.ErrorContains(t, err, "parse") + require.ErrorContains(t, err, "math") + require.ErrorContains(t, err, "asdf") +} + func TestSQLExpressionCellLimitFromConfig(t *testing.T) { tests := []struct { name string