From 56f1d1dfdd3103aeea2220159d34e4ae7f1e43cb Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Wed, 2 Sep 2026 12:15:34 +0300 Subject: [PATCH] return an error on invalid fallback param serialization --- CHANGELOG.md | 6 +++--- tools/search/filter.go | 13 ++++++++----- tools/search/filter_test.go | 28 +++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f47da378..18fef5ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ ## v0.40.2 (WIP) -- Minor filter params replacement optimization. +- Return an error when invalid values are passed as placeholder filter params and optimized params replacements to apply in a single pass. -- Minor UI autocomplete optimizations. +- Fixed collection index parsing error for indexes with missing name. -- Fixed harmless index parsing error when no index name is provided. +- Minor UI autocomplete optimizations _(prefix match, autocomplete debounce, etc.)_. ## v0.40.1 diff --git a/tools/search/filter.go b/tools/search/filter.go index 3025624d..63ce386e 100644 --- a/tools/search/filter.go +++ b/tools/search/filter.go @@ -69,15 +69,18 @@ func (f FilterData) BuildExprWithLimit( case bool, float64, float32, int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8: replacement = cast.ToString(v) default: - replacement = cast.ToString(v) + casted, err := cast.ToStringE(v) // try to json serialize as fallback - if replacement == "" { - raw, _ := json.Marshal(v, json.Deterministic(true)) - replacement = string(raw) + if err != nil { + raw, err := json.Marshal(v, json.Deterministic(true)) + if err != nil { + return nil, fmt.Errorf("failed to serialize param %q: %w", key, err) + } + casted = string(raw) } - replacement = strconv.Quote(replacement) + replacement = strconv.Quote(casted) } replacements = append(replacements, "{:"+key+"}", replacement) diff --git a/tools/search/filter_test.go b/tools/search/filter_test.go index df8fd08f..4c58985c 100644 --- a/tools/search/filter_test.go +++ b/tools/search/filter_test.go @@ -242,13 +242,39 @@ func TestFilterDataBuildExprWithParams(t *testing.T) { t.Fatalf("Expected 1 query, got %d", len(calledQueries)) } - expectedQuery := `SELECT * WHERE ([[test1]] = 1 OR [[test2]] = 0 OR [[test3a]] = 123.456 OR [[test3b]] = 123.456 OR ([[test4]] = '' OR [[test4]] IS NULL) OR [[test5]] = '""' OR [[test6]] = 'simple' OR [[test7]] = '''single_quotes''' OR [[test8]] = '"double_quotes"' OR [[test9]] = '''"quote_with_backslash\' OR [[test10]] = '2023-01-01 00:00:00 +0000 UTC' OR [[test11]] = '["a","''quote","\"quote"]' OR [[test12]] = '{"a":123,"b":"quote\""}' OR [[test13]] = 'a` + expectedQuery := `SELECT * WHERE ([[test1]] = 1 OR [[test2]] = 0 OR [[test3a]] = 123.456 OR [[test3b]] = 123.456 OR ([[test4]] = '' OR [[test4]] IS NULL) OR ([[test5]] = '' OR [[test5]] IS NULL) OR [[test6]] = 'simple' OR [[test7]] = '''single_quotes''' OR [[test8]] = '"double_quotes"' OR [[test9]] = '''"quote_with_backslash\' OR [[test10]] = '2023-01-01 00:00:00 +0000 UTC' OR [[test11]] = '["a","''quote","\"quote"]' OR [[test12]] = '{"a":123,"b":"quote\""}' OR [[test13]] = 'a` expectedQuery += "\nb')" if expectedQuery != calledQueries[0] { t.Fatalf("Expected query \n%s, \ngot \n%s", expectedQuery, calledQueries[0]) } } +func TestFilterDataBuildExprWithParamsFallbackError(t *testing.T) { + t.Parallel() + + resolver := search.NewSimpleFieldResolver("test") + + filter := search.FilterData(`test = {:test}`) + + t.Run("non-string type but valid marshalized json", func(t *testing.T) { + _, err := filter.BuildExpr(resolver, dbx.Params{ + "test": map[string]any{"a": "123"}, + }) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("non-string type but invalid marshalized json", func(t *testing.T) { + _, err := filter.BuildExpr(resolver, dbx.Params{ + "test": map[string]any{"a": "123\xc3"}, + }) + if err == nil { + t.Fatal("Expected filter build error, got nil") + } + }) +} + func TestFilterDataBuildExprWithLimit(t *testing.T) { resolver := search.NewSimpleFieldResolver(`^\w+$`)