Skip to content

Add local mail API rate limiting#1219

Open
bubbmon233 wants to merge 1 commit into
larksuite:mainfrom
bubbmon233:harness/01kt0nkhxkc5595f6cm6wp00s6
Open

Add local mail API rate limiting#1219
bubbmon233 wants to merge 1 commit into
larksuite:mainfrom
bubbmon233:harness/01kt0nkhxkc5595f6cm6wp00s6

Conversation

@bubbmon233
Copy link
Copy Markdown
Collaborator

@bubbmon233 bubbmon233 commented Jun 2, 2026

Generated by the harness-coding skill.

  • Branch: harness/01kt0nkhxkc5595f6cm6wp00s6
  • Target: main

Sprints

ID Title Status Commit
S1 Apply reviewer-directed spec amendments passed
S2 Implement mail API rate limiting in lark-cli passed a140235
S3 Synthesize transport contract for larksuite/cli passed 020aeb8

Source specs


This MR was created autonomously. Quality gates were enforced by the repo's own pre-commit hooks.

Summary by CodeRabbit

  • New Features

    • App-scoped local mail API rate limiting for mail endpoints, including pagination and streaming; responses include retry-after guidance.
  • Bug Fixes

    • Local mail rate-limit errors are preserved and stop automatic retries to avoid misleading wrapping or reclassification.
  • Tests

    • Extensive test coverage: limiter behavior, canonicalization, built-in rules, persistence, concurrency, cross-process sharing, and integration with shortcuts.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds on-device mail API rate limiting: canonical path matching, per-key persisted state with locking and GC, enforcement before token/HTTP execution, preservation of local rate-limit errors through API wrappers and retry loops, and extensive unit and integration tests.

Changes

Mail rate limiting feature

Layer / File(s) Summary
Rules and basic helpers
internal/mailratelimit/rules.go
Defines Rule, Scope, builtinRules, normalizeMethod, and maxRuleWindow.
Canonicalization and tests
internal/mailratelimit/canonical.go, internal/mailratelimit/canonical_test.go
Normalizes method/path inputs, gates mail API prefix, matches exact canonical paths or regex templates, and validates builtin thresholds.
Local error helpers
internal/mailratelimit/errors.go
Constructs structured local rate_limit output.ErrAPI errors with source="local_mailratelimit", exposes IsLocalRateLimit, and provides RetryAfterMs.
Persistent limiter state
internal/mailratelimit/store.go, internal/mailratelimit/store_test.go, internal/mailratelimit/process_test.go
Per-key JSON state files under runtimeDir with per-key locking, GC of expired files, atomic writes with restrictive perms, filename hashing, and tests covering lifecycle, corruption handling, sharing, GC, and lock timeouts.
Limiter enforcement and tests
internal/mailratelimit/limiter.go, internal/mailratelimit/limiter_test.go
Implements Limiter.Check with request-to-rule matching, pruning, per-key storage, retry-after calculation, and tests for thresholds, expiry, multi-rule behavior, key isolation, invalid rules, scope checks, and concurrency.
Client gating and API error preservation
internal/client/client.go, internal/client/api_errors.go, internal/client/client_test.go, internal/client/api_errors_test.go
Runs mail rate-limit checks early in DoSDKRequest/DoStream, preserves local rate-limit errors in WrapDoAPIError, updates pagination to immediately return local limits, and adds client tests asserting limiter ordering and bypass behavior for non-mail endpoints.
Shortcut handling and tests
shortcuts/common/common.go, shortcuts/mail/mail_triage.go, shortcuts/mail/mail_shortcut_test.go
Detects local rate-limit errors in HandleApiResult and triage retry loops to avoid wrapping/retries; adds tests for mail shortcuts to assert limiter-driven failures and single-request behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

size/XL, feature

Suggested reviewers

  • chanthuang
  • liangshuo-1
  • MaxHuang22

🐰 I stitched a fluffy guard by the mail,
Files and rules counting each little trail,
Before tokens fly or HTTP sails,
A gentle hop says “wait” with retry details,
Persistence hums while tests wag their tail.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description does not follow the required template structure. It lacks the Summary, Changes, Test Plan, and Related Issues sections specified in the template. Replace the current description with the required template format, including a brief summary, enumerated list of changes, test plan checklist, and related issues section.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add local mail API rate limiting' clearly and concisely summarizes the main change, accurately reflecting the comprehensive mail rate-limiting implementation across multiple files.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jun 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/mailratelimit/limiter.go (1)

100-116: ⚡ Quick win

Cache compiled regex rules per Limiter (avoid recompiling in match).

Limiter.match calls compileRules(l.rules) on every request; compileRules runs regexp.MustCompile for each rule, so this recompiles all patterns on the hot path. Precompute/store the compiled rules when creating the limiter (e.g., in newLimiter) and reuse them in match*regexp.Regexp supports concurrent MatchString calls.

🤖 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 `@internal/mailratelimit/limiter.go` around lines 100 - 116, The match method
calls compileRules on every invocation, causing repeated regex compilation and
performance overhead. To fix this, modify the Limiter struct to store compiled
rules as a field initialized once (e.g., during newLimiter construction) and
change match to use this precompiled data instead of recompiling on each call.
🤖 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.

Nitpick comments:
In `@internal/mailratelimit/limiter.go`:
- Around line 100-116: The match method calls compileRules on every invocation,
causing repeated regex compilation and performance overhead. To fix this, modify
the Limiter struct to store compiled rules as a field initialized once (e.g.,
during newLimiter construction) and change match to use this precompiled data
instead of recompiling on each call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6debcfed-910c-4042-a11f-4957beb99f7e

📥 Commits

Reviewing files that changed from the base of the PR and between e57d97f and a140235.

📒 Files selected for processing (18)
  • cmd/api/api.go
  • cmd/api/api_test.go
  • cmd/service/service.go
  • cmd/service/service_test.go
  • internal/client/api_errors.go
  • internal/client/api_errors_test.go
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/pagination.go
  • internal/mailratelimit/canonical.go
  • internal/mailratelimit/canonical_test.go
  • internal/mailratelimit/errors.go
  • internal/mailratelimit/limiter.go
  • internal/mailratelimit/limiter_test.go
  • internal/mailratelimit/process_test.go
  • internal/mailratelimit/rules.go
  • internal/mailratelimit/store.go
  • internal/mailratelimit/store_test.go

@github-actions github-actions Bot added the domain/mail PR touches the mail domain label Jun 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/client/client_test.go (1)

275-290: ⚡ Quick win

Test can hang indefinitely on a regression instead of failing cleanly.

PageLimit: 0 (unlimited) combined with a stub that always returns has_more: true + page_token: "next" means the only thing terminating PaginateAll is the local rate limit. If a regression causes the rule to stop matching (e.g., canonicalization or keying change), this loops forever and the test hangs rather than producing an actionable failure. Consider bounding the loop (e.g., a generous PageLimit) so a non-firing limiter fails fast.

Proposed safeguard
-	}, PaginationOptions{PageLimit: 0, PageDelay: -1})
+	}, PaginationOptions{PageLimit: 10, PageDelay: -1})
🤖 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 `@internal/client/client_test.go` around lines 275 - 290, The test uses
PaginateAll with PaginationOptions{PageLimit: 0} which allows an infinite loop
when the stub always returns has_more:true; change the test to use a finite,
generous PageLimit (e.g., 10 or 100) instead of 0 so a missing local rate
limiter will cause the test to fail fast rather than hang; update the call site
passing PaginationOptions to PaginateAll and keep assertions on err being a
rate_limit ExitError and apiCalls expectations adjusted if needed.
internal/client/api_errors_test.go (1)

52-65: 💤 Low value

Reduce drift by reusing the local mail rate-limit error payload in this test

output.ErrAPI(LarkErrRateLimit, ...) already sets ExitError.Detail.Type == "rate_limit" (via lark_errors.go), so the Type part of this assertion isn’t brittle. The drift risk is the local error payload you hand-roll in ExitError.Detail.Detail—especially keys like source: "local_mailratelimit" and retry_after_ms (used by internal/mailratelimit). Reuse/export the internal/mailratelimit constructor (or a small helper) so the test stays coupled to the real contract.

🤖 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 `@internal/client/api_errors_test.go` around lines 52 - 65, The test
TestWrapDoAPIError_PreservesLocalMailRateLimit is hand-constructing the local
mail ratelimit payload (map with "source" and "retry_after_ms") which risks
drift; instead reuse the real constructor from internal/mailratelimit (or export
a small helper) to build the payload passed into output.ErrAPI so the test stays
coupled to the real contract. Update the test to call the mailratelimit payload
constructor (rather than embedding the map) when creating original, keep using
WrapDoAPIError and the same assertions to verify ExitError.Detail is preserved.
🤖 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.

Nitpick comments:
In `@internal/client/api_errors_test.go`:
- Around line 52-65: The test TestWrapDoAPIError_PreservesLocalMailRateLimit is
hand-constructing the local mail ratelimit payload (map with "source" and
"retry_after_ms") which risks drift; instead reuse the real constructor from
internal/mailratelimit (or export a small helper) to build the payload passed
into output.ErrAPI so the test stays coupled to the real contract. Update the
test to call the mailratelimit payload constructor (rather than embedding the
map) when creating original, keep using WrapDoAPIError and the same assertions
to verify ExitError.Detail is preserved.

In `@internal/client/client_test.go`:
- Around line 275-290: The test uses PaginateAll with
PaginationOptions{PageLimit: 0} which allows an infinite loop when the stub
always returns has_more:true; change the test to use a finite, generous
PageLimit (e.g., 10 or 100) instead of 0 so a missing local rate limiter will
cause the test to fail fast rather than hang; update the call site passing
PaginationOptions to PaginateAll and keep assertions on err being a rate_limit
ExitError and apiCalls expectations adjusted if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ea72ab5-b5ae-4673-9dd5-ecbe10ee9ec7

📥 Commits

Reviewing files that changed from the base of the PR and between a140235 and d0e49e1.

📒 Files selected for processing (15)
  • internal/client/api_errors.go
  • internal/client/api_errors_test.go
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/mailratelimit/canonical.go
  • internal/mailratelimit/canonical_test.go
  • internal/mailratelimit/errors.go
  • internal/mailratelimit/limiter.go
  • internal/mailratelimit/limiter_test.go
  • internal/mailratelimit/rules.go
  • internal/mailratelimit/store.go
  • internal/mailratelimit/store_test.go
  • shortcuts/common/common.go
  • shortcuts/mail/mail_shortcut_test.go
  • shortcuts/mail/mail_triage.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/client/client.go
  • internal/mailratelimit/canonical.go

@bubbmon233 bubbmon233 force-pushed the harness/01kt0nkhxkc5595f6cm6wp00s6 branch from d0e49e1 to 60c5e03 Compare June 3, 2026 03:32
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

❌ Patch coverage is 76.45161% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.40%. Comparing base (98173ae) to head (c7c0a97).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
internal/ratelimit/store.go 70.43% 20 Missing and 14 partials ⚠️
internal/ratelimit/limiter.go 75.00% 21 Missing and 5 partials ⚠️
internal/ratelimit/errors.go 69.23% 4 Missing and 4 partials ⚠️
internal/ratelimit/canonical.go 91.66% 2 Missing and 1 partial ⚠️
shortcuts/common/common.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1219      +/-   ##
==========================================
+ Coverage   69.19%   69.40%   +0.21%     
==========================================
  Files         637      644       +7     
  Lines       59753    60107     +354     
==========================================
+ Hits        41345    41718     +373     
+ Misses      15067    15018      -49     
- Partials     3341     3371      +30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add bubbmon233/cli#harness/01kt0nkhxkc5595f6cm6wp00s6 -y -g

@bubbmon233 bubbmon233 force-pushed the harness/01kt0nkhxkc5595f6cm6wp00s6 branch 8 times, most recently from 495a596 to d16bf6b Compare June 3, 2026 07:20
@bubbmon233 bubbmon233 force-pushed the harness/01kt0nkhxkc5595f6cm6wp00s6 branch from d16bf6b to c7c0a97 Compare June 3, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/mail PR touches the mail domain 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