feat: Reserver interface + custom Storage example & Redis/Valkey guide#12
Merged
Conversation
The Storage[K, V] interface is already fully pluggable — NewBucketLimiter accepts any implementation and InMemoryStorage is merely the default — but the repo had no runnable example of a custom store and no guidance on the common "can I back this with Redis?" question. - examples/customstorage: a runnable, goroutine-safe, size-bounded LRU store that caps the number of live keys, demonstrating BYO Storage end to end. - docs/CUSTOM_STORAGE.md: a full guide covering the method contracts (and which ones the manager actually calls), LoadOrStore atomicity, how to test it, and the crucial distinction that Storage is in-process only. It then documents the correct pattern for distributed limiting: a Valkey/Redis-backed Limiter (with a token-bucket Lua script and valkey-go usage) wired through a Storage acting as a key->limiter resolver. - README: expand the Custom storage section to link both. Docs/example only; no library source or public API changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-After A custom Limiter (e.g. a Redis/Valkey-backed one) previously could not drive accurate Retry-After / RateLimit-Reset headers: the middleware had to downcast to the concrete *rate.Limiter to reach the Reserve API, and fell back to Allow-only for anything else. This adds a backend-agnostic reservation seam so any Limiter can be a first-class citizen of header-accurate middleware. - reservation.go: new optional Reserver interface (Reserve() Reservation) and Reservation interface (OK/Delay/Cancel), mirroring the useful subset of *rate.Reservation. - rate.go: NewRateLimiterFunc now returns RateLimiter, a thin wrapper that embeds *rate.Limiter (promoting Allow/Wait/Burst/TokensAt) and implements Reserver. Compile-time assertions included. - examples/middleware: feature-detect Reserver instead of downcasting to *rate.Limiter; enrich RateLimit-Remaining opportunistically via RateLimiter. - rate_test.go: cover Reserver detection, token consume/cancel, and delay. - docs: document Reserver across doc.go, limiter.go, README (core concepts, Allow/Wait/Reserve, features), docs/TOKEN_BUCKET.md, docs/CUSTOM_STORAGE.md (a Valkey Reserver impl), and docs/MIGRATION.md (the *rate.Limiter -> RateLimiter return-type change and how to adapt). BREAKING: code type-asserting manager.GetOrAdd(key) to *rate.Limiter must assert ratelimiter.Reserver (preferred) or ratelimiter.RateLimiter instead. Allow/Wait/ Burst usage is unchanged. go build/vet/test -race all pass; examples run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two related things that make both extension points — bring-your-own store and bring-your-own limiter/middleware — first-class and well-documented.
1. New optional
Reserver/Reservationinterfaces (source feature)Custom limiters (e.g. a Redis/Valkey-backed one) previously couldn't produce accurate
Retry-After/RateLimit-Resetheaders: the middleware had to downcast to the concrete*rate.Limiterand fell back toAllow-only for anything else (old code). This adds a backend-agnostic reservation seam.reservation.go—Reserver(Reserve() Reservation) andReservation(OK/Delay/Cancel), mirroring the useful subset of*rate.Reservation. Both optional; feature-detected via type assertion.rate.go—NewRateLimiterFuncnow returnsRateLimiter, a thin wrapper embedding*rate.Limiter(promotingAllow/Wait/Burst/TokensAt) that also implementsReserver.examples/middleware— now feature-detectsReserver(works with any backend) and enrichesRateLimit-Remainingopportunistically.rate_test.go— covers detection, consume/cancel, and delay.2. Custom
Storageexample + distributed guide (docs)examples/customstorage— runnable, goroutine-safe size-bounded LRU store (go run ./examples/customstorage -cap 2).docs/CUSTOM_STORAGE.md— full guide: method contracts,LoadOrStoreatomicity + a race test, whyStorageis in-process only, and the correct distributed pattern (a Valkey/Redis-backedLimiter+ token-bucket Lua, now also implementingReserver), wired through aStorageresolver. Uses valkey-go.Docs kept in sync
doc.go,limiter.go, README (features, core-concepts table, Allow/Wait/Reserve),docs/TOKEN_BUCKET.md,docs/CUSTOM_STORAGE.md, anddocs/MIGRATION.mdall updated. Mermaid diagrams included.Verification
gofmtclean,go build ./...,go vet ./...,go test -race ./...all passgo docrenders the new symbolsThe Valkey/Redis snippets are clearly labeled illustrative and are not compiled into the module (no
valkey-godependency added togo.mod).🤖 Generated with Claude Code