From 823a55a1efc4003620bcdcf3a33e5789f683e4ea Mon Sep 17 00:00:00 2001 From: / Date: Wed, 27 May 2026 17:31:19 +0800 Subject: [PATCH] fix: update sheets filter view condition validation --- shortcuts/sheets/lark_sheets_filter_views.go | 61 ++++++++-- .../lark_sheets_sheet_filter_view_test.go | 113 ++++++++++++++++++ .../references/lark-sheets-filter-views.md | 7 +- 3 files changed, 173 insertions(+), 8 deletions(-) diff --git a/shortcuts/sheets/lark_sheets_filter_views.go b/shortcuts/sheets/lark_sheets_filter_views.go index ec385a9b..6b0779f0 100644 --- a/shortcuts/sheets/lark_sheets_filter_views.go +++ b/shortcuts/sheets/lark_sheets_filter_views.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "strings" + "unicode/utf8" "github.com/larksuite/cli/internal/output" "github.com/larksuite/cli/internal/validate" @@ -264,7 +265,7 @@ var SheetCreateFilterViewCondition = common.Shortcut{ {Name: "sheet-id", Desc: "sheet ID", Required: true}, {Name: "filter-view-id", Desc: "filter view ID", Required: true}, {Name: "condition-id", Desc: "column letter (e.g. E)", Required: true}, - {Name: "filter-type", Desc: "filter type: hiddenValue, number, text, color", Required: true}, + {Name: "filter-type", Desc: "filter type: multiValue, number, text, color", Required: true}, {Name: "compare-type", Desc: "comparison operator (e.g. less, beginsWith, between)"}, {Name: "expected", Desc: "filter values JSON array (e.g. [\"6\"])", Required: true}, }, @@ -272,7 +273,7 @@ var SheetCreateFilterViewCondition = common.Shortcut{ if _, err := validateFilterViewToken(runtime); err != nil { return err } - return validateExpectedFlag(runtime.Str("expected")) + return validateFilterViewConditionFlags(runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := validateFilterViewToken(runtime) @@ -306,7 +307,7 @@ var SheetUpdateFilterViewCondition = common.Shortcut{ {Name: "sheet-id", Desc: "sheet ID", Required: true}, {Name: "filter-view-id", Desc: "filter view ID", Required: true}, {Name: "condition-id", Desc: "column letter (e.g. E)", Required: true}, - {Name: "filter-type", Desc: "filter type: hiddenValue, number, text, color"}, + {Name: "filter-type", Desc: "filter type: multiValue, number, text, color"}, {Name: "compare-type", Desc: "comparison operator"}, {Name: "expected", Desc: "filter values JSON array"}, }, @@ -319,10 +320,7 @@ var SheetUpdateFilterViewCondition = common.Shortcut{ !hasNonEmptyStringFlag(runtime, "expected") { return common.FlagErrorf("specify at least one of --filter-type, --compare-type, or --expected") } - if s := runtime.Str("expected"); s != "" { - return validateExpectedFlag(s) - } - return nil + return validateFilterViewConditionFlags(runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := validateFilterViewToken(runtime) @@ -469,6 +467,55 @@ func validateExpectedFlag(s string) error { return nil } +func validateFilterViewConditionFlags(runtime *common.RuntimeContext) error { + filterType := strings.TrimSpace(runtime.Str("filter-type")) + if filterType != "" { + switch filterType { + case "multiValue", "number", "text", "color": + case "hiddenValue": + return output.ErrValidation("--filter-type hiddenValue is no longer supported by Lark Sheets filter view conditions; use --filter-type multiValue with --expected values to show, and omit --compare-type") + default: + return output.ErrValidation("--filter-type must be one of multiValue, number, text, color; got %q", filterType) + } + } + + expected := runtime.Str("expected") + if filterType == "multiValue" { + if strings.TrimSpace(runtime.Str("compare-type")) != "" { + return output.ErrValidation("--compare-type must be omitted when --filter-type multiValue") + } + values, err := parseExpectedStringArray(expected) + if err != nil { + return err + } + if len(values) == 0 { + return output.ErrValidation("--expected must contain at least one value when --filter-type multiValue") + } + for i, value := range values { + if utf8.RuneCountInString(value) > 50000 { + return output.ErrValidation("--expected[%d] must be 50000 characters or fewer when --filter-type multiValue", i) + } + } + return nil + } + + if expected != "" { + return validateExpectedFlag(expected) + } + return nil +} + +func parseExpectedStringArray(s string) ([]string, error) { + if strings.TrimSpace(s) == "" { + return nil, output.ErrValidation("--expected is required when --filter-type multiValue") + } + var values []string + if err := json.Unmarshal([]byte(s), &values); err != nil { + return nil, output.ErrValidation("--expected must be a JSON string array when --filter-type multiValue (e.g. [\"A\",\"B\"]), got: %s", s) + } + return values, nil +} + func buildConditionBody(runtime *common.RuntimeContext, includeConditionID bool) map[string]interface{} { body := map[string]interface{}{} if includeConditionID { diff --git a/shortcuts/sheets/lark_sheets_sheet_filter_view_test.go b/shortcuts/sheets/lark_sheets_sheet_filter_view_test.go index a28aec24..f08f8244 100644 --- a/shortcuts/sheets/lark_sheets_sheet_filter_view_test.go +++ b/shortcuts/sheets/lark_sheets_sheet_filter_view_test.go @@ -334,6 +334,104 @@ func TestCreateFilterViewConditionExecuteSuccess(t *testing.T) { } } +func TestCreateFilterViewConditionMultiValueExecuteSuccess(t *testing.T) { + f, stdout, _, reg := cmdutil.TestFactory(t, sheetsTestConfig()) + stub := &httpmock.Stub{ + Method: "POST", URL: "/open-apis/sheets/v3/spreadsheets/shtTOKEN/sheets/sheet1/filter_views/fv1/conditions", + Body: map[string]interface{}{"code": 0, "msg": "success", "data": map[string]interface{}{ + "condition": map[string]interface{}{"condition_id": "C", "filter_type": "multiValue"}, + }}, + } + reg.Register(stub) + err := mountAndRunSheets(t, SheetCreateFilterViewCondition, []string{ + "+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN", + "--sheet-id", "sheet1", "--filter-view-id", "fv1", + "--condition-id", "C", "--filter-type", "multiValue", + "--expected", `["A","B"]`, "--as", "user", + }, f, stdout) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + var body map[string]interface{} + if err := json.Unmarshal(stub.CapturedBody, &body); err != nil { + t.Fatalf("parse body: %v", err) + } + if body["filter_type"] != "multiValue" { + t.Fatalf("unexpected filter_type: %v", body["filter_type"]) + } + if _, ok := body["compare_type"]; ok { + t.Fatalf("multiValue body must omit compare_type: %v", body) + } + expected, ok := body["expected"].([]interface{}) + if !ok || len(expected) != 2 || expected[0] != "A" || expected[1] != "B" { + t.Fatalf("unexpected expected values: %#v", body["expected"]) + } +} + +func TestCreateFilterViewConditionRejectsHiddenValue(t *testing.T) { + f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig()) + err := mountAndRunSheets(t, SheetCreateFilterViewCondition, []string{ + "+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN", + "--sheet-id", "sheet1", "--filter-view-id", "fv1", + "--condition-id", "C", "--filter-type", "hiddenValue", + "--expected", `["A"]`, "--as", "user", + }, f, stdout) + if err == nil { + t.Fatal("expected hiddenValue validation error, got nil") + } + if !strings.Contains(err.Error(), "--filter-type hiddenValue is no longer supported") { + t.Fatalf("unexpected error message: %v", err) + } +} + +func TestCreateFilterViewConditionRejectsInvalidMultiValueFlags(t *testing.T) { + cases := []struct { + name string + args []string + wantText string + }{ + { + name: "compare type", + args: []string{"--compare-type", "less", "--expected", `["A"]`}, + wantText: "--compare-type must be omitted", + }, + { + name: "empty expected", + args: []string{"--expected", `[]`}, + wantText: "at least one value", + }, + { + name: "non-string expected", + args: []string{"--expected", `[1]`}, + wantText: "JSON string array", + }, + { + name: "too long expected", + args: []string{"--expected", `["` + strings.Repeat("x", 50001) + `"]`}, + wantText: "50000 characters", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig()) + args := []string{ + "+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN", + "--sheet-id", "sheet1", "--filter-view-id", "fv1", + "--condition-id", "C", "--filter-type", "multiValue", + } + args = append(args, tc.args...) + args = append(args, "--as", "user") + err := mountAndRunSheets(t, SheetCreateFilterViewCondition, args, f, stdout) + if err == nil { + t.Fatalf("expected validation error for %s, got nil", tc.name) + } + if !strings.Contains(err.Error(), tc.wantText) { + t.Fatalf("unexpected error message: %v", err) + } + }) + } +} + // ── UpdateFilterViewCondition ──────────────────────────────────────────────── func TestUpdateFilterViewConditionDryRun(t *testing.T) { @@ -384,6 +482,21 @@ func TestUpdateFilterViewConditionRejectsNoFields(t *testing.T) { } } +func TestUpdateFilterViewConditionRejectsHiddenValue(t *testing.T) { + f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig()) + err := mountAndRunSheets(t, SheetUpdateFilterViewCondition, []string{ + "+update-filter-view-condition", "--spreadsheet-token", "shtTOKEN", + "--sheet-id", "sheet1", "--filter-view-id", "fv1", "--condition-id", "C", + "--filter-type", "hiddenValue", "--expected", `["A"]`, "--as", "user", + }, f, stdout) + if err == nil { + t.Fatal("expected hiddenValue validation error, got nil") + } + if !strings.Contains(err.Error(), "--filter-type hiddenValue is no longer supported") { + t.Fatalf("unexpected error message: %v", err) + } +} + // ── ListFilterViewConditions ───────────────────────────────────────────────── func TestListFilterViewConditionsDryRun(t *testing.T) { diff --git a/skills/lark-sheets/references/lark-sheets-filter-views.md b/skills/lark-sheets/references/lark-sheets-filter-views.md index 0535799b..52486ea0 100644 --- a/skills/lark-sheets/references/lark-sheets-filter-views.md +++ b/skills/lark-sheets/references/lark-sheets-filter-views.md @@ -123,6 +123,11 @@ lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" \ --sheet-id "" --filter-view-id "" \ --condition-id "G" --filter-type "text" --compare-type "beginsWith" --expected '["a"]' + +# 多值筛选:只展示 Grade 为 A 或 B 的行(multiValue 不传 compare-type) +lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" \ + --sheet-id "" --filter-view-id "" \ + --condition-id "C" --filter-type "multiValue" --expected '["A","B"]' ``` 参数: @@ -134,7 +139,7 @@ lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" | `--sheet-id` | 是 | 工作表 ID | | `--filter-view-id` | 是 | 筛选视图 ID | | `--condition-id` | 是 | 列字母,如 `E` | -| `--filter-type` | 是 | `hiddenValue` / `number` / `text` / `color` | +| `--filter-type` | 是 | `multiValue` / `number` / `text` / `color` | | `--compare-type` | 否 | 比较运算符 | | `--expected` | 是 | 筛选值 JSON 数组 |