Skip to content

Enable middleware support for different quotas per key. - #292

Open
sp1ff wants to merge 3 commits into
boinkor-net:masterfrom
sp1ff:issue-193
Open

Enable middleware support for different quotas per key.#292
sp1ff wants to merge 3 commits into
boinkor-net:masterfrom
sp1ff:issue-193

Conversation

@sp1ff

@sp1ff sp1ff commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

This patch address issue #193 (KeyedRateLimiter with different quota per key)

It adds a method, get_quota() to trait RateLimitingMiddleware. get_quota() returns an Option<Gcra>, and the default implementation simply returns None (largely along the lines suggested by twitu).

As antifuchs anticipated, this has required some restructuring:

  • until now, middleware were simply types (no state)-- now they can have state. I changed RateLimiter to no longer have a phantom member for the middleware, but to actually own its RateLimitingMiddleware instance
  • I was able to preserve the with_middleware() signature by implementing Default on the stateless middlewares, and added a new method use_middleware() that can take stateful middlewares.
  • I moved StateSnapshot into gcra.rs in order to avoid introducing a cyclic dependency between modules gcra and middleware (gcra::NotUntil needs StateSnapshot and RateLimitingMiddleware now needs gcra::Gcra)
  • I made Quota and Gcra public (not just pub(crate)) so that middleware authors that want to support per-key quotas can use them
  • I moved the generic type parameter representing the keys off of RateLimitingMiddleware methods and on to the trait itself. It's only with the introduction of get_quota() that the type becomes important; it really can't be anything, but needs to line-up with the key type parameter to RateLimiter.

More tests, additional constructors, and field testing are needed, but I want to post this to begin gathering feedback.

@codecov

codecov Bot commented Dec 18, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.48980% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.71%. Comparing base (0ee1a70) to head (e763713).
⚠️ Report is 12 commits behind head on master.

Files with missing lines Patch % Lines
governor/src/middleware.rs 98.94% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #292      +/-   ##
==========================================
+ Coverage   97.61%   97.71%   +0.09%     
==========================================
  Files          32       32              
  Lines        1974     2101     +127     
==========================================
+ Hits         1927     2053     +126     
- Misses         47       48       +1     

☔ 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.

@antifuchs

Copy link
Copy Markdown
Collaborator

Wowza, @sp1ff, that's really cool. I've only superficially read over it so far, but it does sound very reasonable. I see a bunch of tests that are failing, so I'll treat this as a draft until you give me the go-ahead, but great work so far!

@sp1ff

sp1ff commented Dec 18, 2025

Copy link
Copy Markdown
Contributor Author

OK, cool. I saw the failed tests, but I didn't want to spend time fixing them if your feedback was something like "this is a bad idea." Since you're receptive, my plan is to work a bit on a crate I want to build on top of governor (with a path dependency to my local fork) to gauge usability, then come back to this PR (I think it also needs to be rebased).

This patch address issue boinkor-net#193 (KeyedRateLimiter with different quota per key)

It adds a method, `get_quota()` to trait `RateLimitingMiddleware`.
`get_quota()` returns an `Option<Gcra>`, and the default implementation
simply returns `None` (largely along the lines suggested by twitu).

As antifuchs anticipated, this has required some restructuring:

    - until now, middleware were simply types (no state)-- now
      they can have state. I changed `RateLimiter` to no longer
      have a phantom member for the middleware, but to actually
      own its `RateLimitingMiddleware` instance
    - I was able to preserve the `with_middleware()` signature
      by implementing `Default` on the stateless middlewares,
      and added a new method `use_middleware()` that can take
      stateful middlewares.
    - I moved `StateSnapshot` into gcra.rs in order to avoid
      introducing a cyclic dependency between modules gcra and
      middleware (`gcra::NotUntil` needs `StateSnapshot` and
      `RateLimitingMiddleware` now needs `gcra::Gcra`)
    - I made `Quota` and `Gcra` public (not just pub(crate))
      so that middleware authors that want to support per-key
      quotas can use them
    - I moved the generic type parameter representing the keys
      off of `RateLimitingMiddleware` methods and on to the
      trait itself. It's only with the introduction of
      `get_quota()` that the type becomes important; it really
      can't be *anything*, but needs to line-up with the
      key type parameter to `RateLimiter`.

More tests, additional constructors, and field testing are needed,
but I want to post this to begin gathering feedback.
This is likely to be more convenient for implementors of keyed
middleware; this way they won't have to fuss with locks, proxies,
and all that there.

This patch also updates `check_key_n()` to check the middleware
for per-key quotas and to use this idiom.
@sp1ff
sp1ff force-pushed the issue-193 branch 2 times, most recently from 1eb221d to 3b8515d Compare December 24, 2025 03:46
@sp1ff

sp1ff commented Dec 24, 2025

Copy link
Copy Markdown
Contributor Author

Alright. I'd like to add docs & squash my commits, but I think it's ready for review.

Do you think this commit should include a few implementations of middleware that support per-key quotas (say, one based on HashMap & one on DashMap)? Or should that be done in a separate PR?

@sp1ff

sp1ff commented Jan 19, 2026

Copy link
Copy Markdown
Contributor Author

@antifuchs Any chance I could get a review on this? I have a crate built on it that I'd like to publish (but crates.io won't allow dependencies outside itself, like a "git" dependency to my fork of this project).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants