# Guard — programmatic rate limiting for lambda routes `Guard` is the rate-limit API for lambda (inline) route registrations. It produces a `Middleware` that is composed once at route-wiring time — the resolver lambda is captured directly into the closure, with no map lookup on the request hot-path. ## Obtaining Guard `Guard` is provided in the `FlashContext` after `LimiterExtension` is installed: ```java Guard guard = app.ctx().require(Guard.class); ``` Or inside another extension: ```java public void install(FlashRegistrar app, FlashContext ctx) { Guard guard = ctx.require(Guard.class); // ... } ``` ## API ```java // Fixed window (default strategy) Middleware limit(String resolverKey, int requests, long window, TimeUnit unit) // Explicit strategy Middleware limit(String resolverKey, int requests, long window, TimeUnit unit, LimitStrategy strategy) ``` Both overloads: - Resolve the named key lambda **once** at call time (fail-fast if unknown). - Return a stateless `Middleware` whose closure captures the lambda and `LimitConfig` directly. - Share the `BucketStore` with all other rules registered through this `LimiterExtension` instance. ## Examples ### Simple per-IP limit on a lambda route ```java Guard guard = app.ctx().require(Guard.class); app.get("/api/search", (req, res) -> searchService.query(req.query("q"))) .with(guard.limit("ip", 100, 1, TimeUnit.SECONDS)); ``` ### Per authenticated user — token bucket ```java app.post("/api/export", (req, res) -> exportService.run(req)) .with(guard.limit("auth_user", 10, 1, TimeUnit.MINUTES, LimitStrategy.TOKEN_BUCKET)); ``` ### Chaining with other middleware `Guard.limit(...)` returns a plain `Middleware`, so it composes with `Middleware.of()` and `.andThen()` exactly like any other middleware: ```java Middleware secured = Middleware.of( guard.limit("ip", 200, 1, TimeUnit.SECONDS), // ← outermost: runs first oidc.protect() ); app.get("/dashboard", handler).with(secured); ``` Or with `.andThen()` for two middlewares: ```java app.get("/dashboard", handler) .with(guard.limit("ip", 200, 1, TimeUnit.SECONDS).andThen(oidc.protect())); ``` ### Different limits on the same path by method ```java // Read: 500/s; Write: 20/s app.get("/api/items", readHandler) .with(guard.limit("ip", 500, 1, TimeUnit.SECONDS)); app.post("/api/items", writeHandler).with(guard.limit("ip", 20, 1, TimeUnit.SECONDS)); ``` Each `.with(guard.limit(...))` call creates an independent bucket store key namespace — GET and POST requests to `/api/items` share the same IP bucket only if you share the same `Middleware` instance. Using two `guard.limit(...)` calls creates **two independent buckets**. ### Reusing a middleware instance across routes To share a single bucket pool across multiple routes (treating them as one combined limit): ```java Middleware sharedIpLimit = guard.limit("ip", 1000, 1, TimeUnit.MINUTES); app.get("/api/items", handler1).with(sharedIpLimit); app.get("/api/items/{id}", handler2).with(sharedIpLimit); app.post("/api/items", handler3).with(sharedIpLimit); ``` All three routes now draw from the same per-IP bucket — 1000 combined requests per minute. ### Inside an extension ```java public class MyApiExtension implements FlashExtension { public void install(FlashRegistrar app, FlashContext ctx) { Guard guard = ctx.require(Guard.class); // LimiterExtension must be installed first Middleware ipLimit = guard.limit("ip", 60, 1, TimeUnit.SECONDS); app.get("/api/status", statusHandler) .with(ipLimit); app.get("/api/metrics", metricsHandler).with(ipLimit); } } ``` ### Large window ```java app.get("/api/export", exportHandler) .with(guard.limit("api_key", 50, 24, TimeUnit.HOURS)); ``` ## Fail-fast If the resolver name is not registered, `guard.limit(...)` throws immediately (at wiring time, not at request time): ``` InitializationException: Rate-limit resolver "auth_user" is not registered. ``` ## Comparison: Guard vs @Limit | | `@Limit` | `Guard.limit(...)` | |---|---|---| | Route style | Class-based `RequestHandler` | Lambda `(req, res) -> ...` | | Configuration | Annotation fields | Method arguments | | Where resolved | `AnnotationProcessor` at `scan()` | `guard.limit(...)` call at wiring | | Hot-path overhead | Zero | Zero | | Fail-fast | Yes | Yes | | Composable with `Middleware.of()` | Via annotation processor order | Yes, directly |