Skip to content

Complete mail rule reorder IDs#1729

Open
bubbmon233 wants to merge 1 commit into
larksuite:mainfrom
bubbmon233:feat/275cd33
Open

Complete mail rule reorder IDs#1729
bubbmon233 wants to merge 1 commit into
larksuite:mainfrom
bubbmon233:feat/275cd33

Conversation

@bubbmon233

@bubbmon233 bubbmon233 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Completes the mail rule reorder command by expanding partial rule ID input into the full current rule order before calling the reorder endpoint.

Changes:

  • Adds rule ID validation and merge logic for selected reordered IDs.
  • Lists existing mail rules before submitting the reorder request.
  • Adds dry-run output and tests for the rule reorder flow.

Summary by CodeRabbit

  • New Features

    • Added support for reordering mail rules with partial input, automatically completing the full rule order before execution.
    • Added a dry-run preview that shows the steps needed to list current rules and submit the reorder request.
  • Bug Fixes

    • Improved validation for rule ID lists, including empty values, duplicates, and invalid payload shapes.
    • Preserved the existing order of unspecified rules while applying the requested reordering.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new mail rule reorder operation. It detects the reorder method, completes requests by merging user-supplied rule_ids with the current server-side order, supports a dry-run mode with a two-step plan, and wires this logic into the existing service execution flow with accompanying tests.

Changes

Mail Rule Reorder Feature

Layer / File(s) Summary
Method detection and rule_ids validation
cmd/service/mail_rules_reorder.go
Adds a matcher for the reorder method and parses/validates --data.rule_ids (type, non-empty, no duplicates/empties).
Merge logic, current rule lookup, and completion
cmd/service/mail_rules_reorder.go
Fetches current rule order via GET, merges input IDs with current IDs preserving relative order, clones request data, and completes the request.
Dry-run plan and output rendering
cmd/service/mail_rules_reorder.go
Builds a two-step dry-run description (list then reorder) and renders pretty/JSON output.
Service execution wiring
cmd/service/service.go
Routes dry-run and non-dry-run execution paths to the new reorder completion and dry-run functions when the method is detected.
Tests: merge, validation, and command execution
cmd/service/mail_rules_reorder_test.go
Adds helpers and tests for merge semantics, rule_ids validation, completion execution, dry-run output, and error passthrough.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as Service CLI
  participant Service as serviceMethodRun
  participant Reorder as MailRuleReorder logic
  participant API as Mail API

  CLI->>Service: run reorder command
  alt dry-run
    Service->>Reorder: mailRuleReorderDryRun(request)
    Reorder->>Reorder: extractRuleIDs & validate
    Reorder->>API: GET current rules (described)
    Reorder-->>Service: two-step dry-run plan output
  else execute
    Service->>Reorder: completeMailRuleReorder(request)
    Reorder->>Reorder: extractRuleIDs & validate
    Reorder->>API: GET current rules
    API-->>Reorder: current rule IDs
    Reorder->>Reorder: mergeRuleIDs(input, current)
    Reorder-->>Service: updated request
    Service->>API: POST reorder with merged rule_ids
    API-->>Service: response
  end
  Service-->>CLI: result/output
Loading

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: completing mail rule reorder IDs.
Description check ✅ Passed The description covers the summary and main changes, but it omits the Test Plan and Related Issues sections from the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@705d2bc3066e02f87caf0cd34a41517cad59ca24

🧩 Skill update

npx skills add bubbmon233/cli#feat/275cd33 -y -g

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

  • deterministic-gate — failure — details
  • results — failure — details

deterministic-gate

  • public_content_generic_credentialcmd/service/mail_rules_reorder_test.go:121 — public contribution contains a generic credential assignment — Action: remove the value from the public contribution and replace it with a non-sensitive placeholder

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
cmd/service/mail_rules_reorder.go (2)

27-44: 🗄️ Data Integrity & Integration | 🔵 Trivial

TOCTOU window between listing current order and posting the reorder.

completeMailRuleReorder reads the current rule order via GET, computes the merged order, then a separate POST commits it (in service.go). If another session/process changes the rule set between these two calls, the completed rule_ids sent could be stale (missing a newly added rule, or containing a since-deleted one), and the reorder could fail or silently drop new rules from the order.

Is there a conditional-write mechanism (e.g., If-Match/version token) on the reorder endpoint that this could leverage, or is this window an accepted limitation for now?

Also applies to: 121-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder.go` around lines 27 - 44, The reorder flow in
completeMailRuleReorder has a TOCTOU gap because it fetches current IDs with
listMailRuleIDs and then later submits the merged rule_ids, so the final POST
can be based on stale state. Update the reorder path to use any available
conditional-write/version mechanism on the APIClient request (for example, an
If-Match or revision token captured alongside the GET) or, if none exists, make
the limitation explicit in the service.go reorder handling and guard against
stale/missing IDs as much as possible in completeMailRuleReorder, mergeRuleIDs,
and the caller that submits the request.

143-159: 📐 Maintainability & Code Quality | 🔵 Trivial

Malformed/unexpected list response shape is silently treated as "no rules".

If the response lacks a data/items object or the shapes don't match expectations, extractCurrentRuleIDs quietly returns an empty slice. Downstream, this surfaces as a generic "unknown rule ID" validation error for every input ID rather than signaling a response-shape problem, which could confuse debugging of an actual API/schema change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder.go` around lines 143 - 159,
extractCurrentRuleIDs is treating malformed list responses as an empty result,
which hides API/schema issues and later shows up as misleading unknown rule ID
errors. Update extractCurrentRuleIDs to validate the expected result -> data ->
items shape and distinguish “no rules” from “unexpected response shape”; if any
required map/slice cast fails, return an explicit error (or propagate one)
instead of silently returning an empty slice. Then adjust the caller in
mail_rules_reorder.go to handle that error path and surface a clear
response-shape failure before validation proceeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/service/mail_rules_reorder_test.go`:
- Around line 88-117: The validation test only checks the error type and message
substring, but it should assert the typed metadata too. Update
TestExtractRuleIDsValidation to verify the ValidationError from extractRuleIDs
carries the expected subtype and param values, using errs.ProblemOf or direct
field checks on validationErr, and keep the existing type assertion while
removing reliance on strings.Contains for the main validation.
- Around line 41-86: The unknown-id test in TestMergeRuleIDs should assert the
typed ValidationError metadata returned by mergeRuleIDs, not just a substring of
err.Error(). Update the "unknown id" case to verify the error is an
errs.ProblemOf with the expected category/subtype and Param("--data.rule_ids"),
while still checking the cause/content as needed; keep the successful merge
cases unchanged.

In `@cmd/service/mail_rules_reorder.go`:
- Around line 91-119: `mergeRuleIDs` can panic if `currentIDs` contains
duplicate rule IDs because it indexes `inputIDs[nextInput]` without checking
bounds. Update the logic in `mergeRuleIDs` to guard the `nextInput` access when
rebuilding `out`, and fail safely if the reordered traversal would consume more
selected IDs than are available. Keep the existing validation/error style in
this function so the fix is localized and easy to trace.
- Around line 170-202: The dry-run output currently prints the “=== Dry Run ===”
banner to the same stream as JSON, which breaks stdout-only machine parsing in
json mode. Update mailRuleReorderDryRun and printDryRunAPI so the banner is only
emitted for pretty output or sent to stderr, and make the same stdout-only
adjustment in internal/cmdutil.PrintDryRun and PrintDryRunWithFile while
preserving the JSON body behavior.

---

Nitpick comments:
In `@cmd/service/mail_rules_reorder.go`:
- Around line 27-44: The reorder flow in completeMailRuleReorder has a TOCTOU
gap because it fetches current IDs with listMailRuleIDs and then later submits
the merged rule_ids, so the final POST can be based on stale state. Update the
reorder path to use any available conditional-write/version mechanism on the
APIClient request (for example, an If-Match or revision token captured alongside
the GET) or, if none exists, make the limitation explicit in the service.go
reorder handling and guard against stale/missing IDs as much as possible in
completeMailRuleReorder, mergeRuleIDs, and the caller that submits the request.
- Around line 143-159: extractCurrentRuleIDs is treating malformed list
responses as an empty result, which hides API/schema issues and later shows up
as misleading unknown rule ID errors. Update extractCurrentRuleIDs to validate
the expected result -> data -> items shape and distinguish “no rules” from
“unexpected response shape”; if any required map/slice cast fails, return an
explicit error (or propagate one) instead of silently returning an empty slice.
Then adjust the caller in mail_rules_reorder.go to handle that error path and
surface a clear response-shape failure before validation proceeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a51e2db-0195-44ba-bfcb-930f06e8f1a3

📥 Commits

Reviewing files that changed from the base of the PR and between c2d6038 and 705d2bc.

📒 Files selected for processing (3)
  • cmd/service/mail_rules_reorder.go
  • cmd/service/mail_rules_reorder_test.go
  • cmd/service/service.go

Comment on lines +41 to +86
func TestMergeRuleIDs(t *testing.T) {
tests := []struct {
name string
input []string
current []string
want []string
wantErr string
}{
{
name: "complete input stays unchanged",
input: []string{"r3", "r2", "r1"},
current: []string{"r1", "r2", "r3"},
want: []string{"r3", "r2", "r1"},
},
{
name: "partial input fills original selected slots",
input: []string{"r3", "r1"},
current: []string{"r1", "r2", "r3", "r4"},
want: []string{"r3", "r2", "r1", "r4"},
},
{
name: "unknown id",
input: []string{"r9"},
current: []string{"r1", "r2"},
wantErr: "unknown rule ID",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := mergeRuleIDs(tt.input, tt.current)
if tt.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("mergeRuleIDs error = %v, want containing %q", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("mergeRuleIDs unexpected error: %v", err)
}
if strings.Join(got, ",") != strings.Join(tt.want, ",") {
t.Fatalf("mergeRuleIDs = %v, want %v", got, tt.want)
}
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Assert typed error metadata, not just message substrings.

The "unknown id" case only checks err.Error() contains "unknown rule ID". mergeRuleIDs returns a typed errs.ValidationError with SubtypeFailedPrecondition and Param("--data.rule_ids") (per mergeRuleIDs in mail_rules_reorder.go); asserting only the message text won't catch regressions in classification/param, and won't survive wording changes.

🔧 Proposed fix
 		{
 			name:    "unknown id",
 			input:   []string{"r9"},
 			current: []string{"r1", "r2"},
-			wantErr: "unknown rule ID",
 		},
...
 			got, err := mergeRuleIDs(tt.input, tt.current)
-			if tt.wantErr != "" {
-				if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
-					t.Fatalf("mergeRuleIDs error = %v, want containing %q", err, tt.wantErr)
-				}
+			if tt.name == "unknown id" {
+				requireProblem(t, err, errs.CategoryValidation, errs.SubtypeFailedPrecondition, 0)
 				return
 			}

As per path instructions, **/*_test.go: "Error-path tests must assert typed metadata via errs.ProblemOf (category / subtype / param) and cause preservation, not message substrings alone".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestMergeRuleIDs(t *testing.T) {
tests := []struct {
name string
input []string
current []string
want []string
wantErr string
}{
{
name: "complete input stays unchanged",
input: []string{"r3", "r2", "r1"},
current: []string{"r1", "r2", "r3"},
want: []string{"r3", "r2", "r1"},
},
{
name: "partial input fills original selected slots",
input: []string{"r3", "r1"},
current: []string{"r1", "r2", "r3", "r4"},
want: []string{"r3", "r2", "r1", "r4"},
},
{
name: "unknown id",
input: []string{"r9"},
current: []string{"r1", "r2"},
wantErr: "unknown rule ID",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := mergeRuleIDs(tt.input, tt.current)
if tt.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("mergeRuleIDs error = %v, want containing %q", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("mergeRuleIDs unexpected error: %v", err)
}
if strings.Join(got, ",") != strings.Join(tt.want, ",") {
t.Fatalf("mergeRuleIDs = %v, want %v", got, tt.want)
}
})
}
}
func TestMergeRuleIDs(t *testing.T) {
tests := []struct {
name string
input []string
current []string
want []string
wantErr string
}{
{
name: "complete input stays unchanged",
input: []string{"r3", "r2", "r1"},
current: []string{"r1", "r2", "r3"},
want: []string{"r3", "r2", "r1"},
},
{
name: "partial input fills original selected slots",
input: []string{"r3", "r1"},
current: []string{"r1", "r2", "r3", "r4"},
want: []string{"r3", "r2", "r1", "r4"},
},
{
name: "unknown id",
input: []string{"r9"},
current: []string{"r1", "r2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := mergeRuleIDs(tt.input, tt.current)
if tt.name == "unknown id" {
requireProblem(t, err, errs.CategoryValidation, errs.SubtypeFailedPrecondition, 0)
return
}
if err != nil {
t.Fatalf("mergeRuleIDs unexpected error: %v", err)
}
if strings.Join(got, ",") != strings.Join(tt.want, ",") {
t.Fatalf("mergeRuleIDs = %v, want %v", got, tt.want)
}
})
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder_test.go` around lines 41 - 86, The unknown-id
test in TestMergeRuleIDs should assert the typed ValidationError metadata
returned by mergeRuleIDs, not just a substring of err.Error(). Update the
"unknown id" case to verify the error is an errs.ProblemOf with the expected
category/subtype and Param("--data.rule_ids"), while still checking the
cause/content as needed; keep the successful merge cases unchanged.

Source: Path instructions

Comment on lines +88 to +117
func TestExtractRuleIDsValidation(t *testing.T) {
tests := []struct {
name string
data any
wantErr string
}{
{name: "missing", data: map[string]any{}, wantErr: "string array"},
{name: "not array", data: map[string]any{"rule_ids": "r1"}, wantErr: "string array"},
{name: "non string", data: map[string]any{"rule_ids": []any{"r1", 2}}, wantErr: "string array"},
{name: "empty array", data: map[string]any{"rule_ids": []any{}}, wantErr: "at least one"},
{name: "empty id", data: map[string]any{"rule_ids": []any{""}}, wantErr: "must not contain empty"},
{name: "duplicate id", data: map[string]any{"rule_ids": []any{"r1", "r1"}}, wantErr: "duplicate"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := extractRuleIDs(tt.data)
if err == nil {
t.Fatal("expected validation error")
}
var validationErr *errs.ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("expected ValidationError, got %T: %v", err, err)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("error = %v, want containing %q", err, tt.wantErr)
}
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Also assert Subtype/Param here, not just error type + message.

errors.As confirms the error is a *errs.ValidationError, but the test never reads validationErr.Subtype/validationErr.Param — it falls back to strings.Contains for the actual assertion. All these cases map to SubtypeInvalidArgument with Param("--data.rule_ids") per extractRuleIDs; asserting those fields directly would satisfy the guideline and be more robust than string matching.

As per path instructions, **/*_test.go: "Error-path tests must assert typed metadata via errs.ProblemOf (category / subtype / param) and cause preservation, not message substrings alone".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder_test.go` around lines 88 - 117, The validation
test only checks the error type and message substring, but it should assert the
typed metadata too. Update TestExtractRuleIDsValidation to verify the
ValidationError from extractRuleIDs carries the expected subtype and param
values, using errs.ProblemOf or direct field checks on validationErr, and keep
the existing type assertion while removing reliance on strings.Contains for the
main validation.

Source: Path instructions

Comment on lines +91 to +119
func mergeRuleIDs(inputIDs, currentIDs []string) ([]string, error) {
inputSet := map[string]struct{}{}
for _, id := range inputIDs {
inputSet[id] = struct{}{}
}
currentSet := map[string]struct{}{}
for _, id := range currentIDs {
currentSet[id] = struct{}{}
}
for _, id := range inputIDs {
if _, ok := currentSet[id]; !ok {
return nil, errs.NewValidationError(errs.SubtypeFailedPrecondition, "unknown rule ID in --data.rule_ids: %s", id).
WithParam("--data.rule_ids").
WithHint("run: lark-cli mail user_mailbox.rules list --params '{\"user_mailbox_id\":\"<mailbox>\"}' --as user")
}
}

nextInput := 0
out := make([]string, 0, len(currentIDs))
for _, id := range currentIDs {
if _, selected := inputSet[id]; selected {
out = append(out, inputIDs[nextInput])
nextInput++
continue
}
out = append(out, id)
}
return out, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against index-out-of-range if currentIDs contains a duplicate.

mergeRuleIDs only validates that each input ID exists in currentSet (a set), not that currentIDs itself is duplicate-free. If the list endpoint ever returns the same rule ID twice, inputIDs[nextInput] can be indexed past len(inputIDs)-1 for the second occurrence, panicking. Unlikely from a well-behaved API, but there's no fallback here — it's a hard crash, not a safe degradation.

🛡️ Proposed bounds guard
 	nextInput := 0
 	out := make([]string, 0, len(currentIDs))
 	for _, id := range currentIDs {
 		if _, selected := inputSet[id]; selected {
+			if nextInput >= len(inputIDs) {
+				return nil, errs.NewInternalError(errs.SubtypeUnknown, "unexpected duplicate rule ID in current order: %s", id)
+			}
 			out = append(out, inputIDs[nextInput])
 			nextInput++
 			continue
 		}
 		out = append(out, id)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func mergeRuleIDs(inputIDs, currentIDs []string) ([]string, error) {
inputSet := map[string]struct{}{}
for _, id := range inputIDs {
inputSet[id] = struct{}{}
}
currentSet := map[string]struct{}{}
for _, id := range currentIDs {
currentSet[id] = struct{}{}
}
for _, id := range inputIDs {
if _, ok := currentSet[id]; !ok {
return nil, errs.NewValidationError(errs.SubtypeFailedPrecondition, "unknown rule ID in --data.rule_ids: %s", id).
WithParam("--data.rule_ids").
WithHint("run: lark-cli mail user_mailbox.rules list --params '{\"user_mailbox_id\":\"<mailbox>\"}' --as user")
}
}
nextInput := 0
out := make([]string, 0, len(currentIDs))
for _, id := range currentIDs {
if _, selected := inputSet[id]; selected {
out = append(out, inputIDs[nextInput])
nextInput++
continue
}
out = append(out, id)
}
return out, nil
}
func mergeRuleIDs(inputIDs, currentIDs []string) ([]string, error) {
inputSet := map[string]struct{}{}
for _, id := range inputIDs {
inputSet[id] = struct{}{}
}
currentSet := map[string]struct{}{}
for _, id := range currentIDs {
currentSet[id] = struct{}{}
}
for _, id := range inputIDs {
if _, ok := currentSet[id]; !ok {
return nil, errs.NewValidationError(errs.SubtypeFailedPrecondition, "unknown rule ID in --data.rule_ids: %s", id).
WithParam("--data.rule_ids").
WithHint("run: lark-cli mail user_mailbox.rules list --params '{\"user_mailbox_id\":\"<mailbox>\"}' --as user")
}
}
nextInput := 0
out := make([]string, 0, len(currentIDs))
for _, id := range currentIDs {
if _, selected := inputSet[id]; selected {
if nextInput >= len(inputIDs) {
return nil, errs.NewInternalError(errs.SubtypeUnknown, "unexpected duplicate rule ID in current order: %s", id)
}
out = append(out, inputIDs[nextInput])
nextInput++
continue
}
out = append(out, id)
}
return out, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder.go` around lines 91 - 119, `mergeRuleIDs` can
panic if `currentIDs` contains duplicate rule IDs because it indexes
`inputIDs[nextInput]` without checking bounds. Update the logic in
`mergeRuleIDs` to guard the `nextInput` access when rebuilding `out`, and fail
safely if the reordered traversal would consume more selected IDs than are
available. Keep the existing validation/error style in this function so the fix
is localized and easy to trace.

Comment on lines +170 to +202
func mailRuleReorderDryRun(f *cmdutil.Factory, request client.RawApiRequest, config *core.CliConfig, format string) error {
if _, err := extractRuleIDs(request.Data); err != nil {
return err
}
dr := cmdutil.NewDryRunAPI().
Desc("mail rule reorder completes partial rule_ids at execution time").
GET(strings.TrimSuffix(request.URL, "/reorder")).
Desc("Step 1: list current mail rules to read the full rule ID order").
POST(request.URL).
Desc("Step 2: replace --data.rule_ids with the completed full order, then reorder")
if !util.IsNil(request.Data) {
dr.Body(map[string]any{
"rule_ids": "<completed from current list; selected slots keep current positions and use input order>",
"input": request.Data,
})
}
dr.Set("as", string(request.As))
dr.Set("appId", config.AppID)
if config.UserOpenId != "" {
dr.Set("userOpenId", config.UserOpenId)
}
return printDryRunAPI(f.IOStreams.Out, dr, format)
}

func printDryRunAPI(w io.Writer, dr *cmdutil.DryRunAPI, format string) error {
fmt.Fprintln(w, "=== Dry Run ===")
if format == "pretty" {
fmt.Fprint(w, dr.Format())
return nil
}
output.PrintJson(w, dr)
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether other dry-run paths mix a header with JSON on stdout.
rg -n "Dry Run" -C3 --type=go
rg -n "func PrintJson" -C5 internal/output
rg -n "func serviceDryRun|func.*PrintDryRunWithFile" -C10 --type=go

Repository: larksuite/cli

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant dry-run helpers and JSON printer implementations.
git ls-files | rg '(^|/)(cmd/service/mail_rules_reorder\.go|internal/output|cmdutil|serviceDryRun|PrintDryRunWithFile|dry.?run).*\.go$' || true

# Find all dry-run related stdout/header strings and helper names.
rg -n "Dry Run|PrintDryRunWithFile|serviceDryRun|PrintJson\(" . --type=go

# Inspect the target file and the likely shared helpers.
for f in cmd/service/mail_rules_reorder.go; do
  echo "### $f"
  wc -l "$f"
  cat -n "$f" | sed -n '150,230p'
done

# Inspect JSON printing behavior.
for f in $(git ls-files | rg '^internal/output/.*\.go$'); do
  echo "### $f"
  wc -l "$f"
  cat -n "$f" | sed -n '1,220p'
done

# Inspect any shared dry-run helpers that might already handle headers separately.
for f in $(git ls-files | rg '(^|/)cmdutil/.*\.go$|(^|/)cmd/.*\.go$'); do
  if rg -n "PrintDryRunWithFile|serviceDryRun|Dry Run|DryRun" "$f" >/dev/null 2>&1; then
    echo "### $f"
    wc -l "$f"
    cat -n "$f" | sed -n '1,260p'
  fi
done

Repository: larksuite/cli

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the shared dry-run helper and the service branch that calls it.
cat -n internal/cmdutil/dryrun.go | sed -n '200,320p'
cat -n cmd/service/service.go | sed -n '620,690p'

# Check the dry-run tests and any contract assertions around stdout/JSON.
cat -n internal/cmdutil/dryrun_test.go | sed -n '1,220p'
cat -n cmd/service/service_test.go | sed -n '280,340p'
cat -n cmd/service/service_test.go | sed -n '1060,1105p'

Repository: larksuite/cli

Length of output: 16358


Dry-run JSON should stay stdout-only
printDryRunAPI writes === Dry Run === before the JSON body, and internal/cmdutil.PrintDryRun / PrintDryRunWithFile do the same. In --format json mode this breaks machine parsing; move the banner to stderr or gate it to pretty output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/service/mail_rules_reorder.go` around lines 170 - 202, The dry-run output
currently prints the “=== Dry Run ===” banner to the same stream as JSON, which
breaks stdout-only machine parsing in json mode. Update mailRuleReorderDryRun
and printDryRunAPI so the banner is only emitted for pretty output or sent to
stderr, and make the same stdout-only adjustment in internal/cmdutil.PrintDryRun
and PrintDryRunWithFile while preserving the JSON body behavior.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant