RouteConfig and the route runtime
import 'package:gisila/gisila.dart';RouteConfig
Section titled “RouteConfig”Declarative, per-route configuration. An empty RouteConfig changes nothing, so configs can be layered.
class RouteConfig { const RouteConfig({ RateLimitConfig? rateLimit, Duration? timeout, int? maxBodyBytes, Set<String>? contentTypes, CorsConfig? cors, List<Middleware> middleware = const [], bool requireAuth = false, Set<String> permissions = const {}, Set<String> anyPermission = const {}, Set<String> roles = const {}, int? statusCode, bool isPublic = false, });
static const empty = RouteConfig();
RouteConfig merge(RouteConfig? other); RouteConfig operator +(RouteConfig? other); RouteConfig copyWith({...}); bool get hasAuthGuards;}When to use: when one route (or one controller) needs different limits from the rest: a tight rate limit on login, a longer timeout on a report, a larger body cap on uploads, JSON-only input. Pass it as config: on a route annotation, in @UseRouteConfig on a controller, or as AppConfig.defaultRouteConfig for every route.
| Field | Type | Default | Description |
|---|---|---|---|
rateLimit |
RateLimitConfig? |
null |
A limiter just for this route (each route gets its own buckets). Applies in addition to AppConfig.rateLimit, so a request must pass both. |
timeout |
Duration? |
null |
Answer 504 if the route (its middleware, guard and handler) takes longer. Applies in addition to AppConfig.requestTimeout; the shorter wins. |
maxBodyBytes |
int? |
null |
Reject bodies above this size with 413. Applies in addition to AppConfig.maxRequestBodyBytes. |
contentTypes |
Set<String>? |
null |
Allowed Content-Type values (without parameters) for POST, PUT and PATCH. Anything else, including a missing header, is a 415. |
cors |
CorsConfig? |
null |
CORS headers for this route’s responses. Pre-flight OPTIONS requests only reach a route that is itself an OPTIONS route, so keep pre-flights on the app-wide AppConfig.cors. |
middleware |
List<Middleware> |
[] |
Extra Shelf middleware, run in list order after the built-in per-route checks and before the timeout and guard. Middleware isn’t const, so annotations can’t hold it; use @UseMiddleware there. |
requireAuth |
bool |
false |
Require an authenticated principal. |
permissions |
Set<String> |
{} |
The principal must hold every one. |
anyPermission |
Set<String> |
{} |
The principal must hold at least one. |
roles |
Set<String> |
{} |
The principal must hold at least one. |
statusCode |
int? |
null |
Carried through merges, but the generated code takes the success status from the annotation’s statusCode, so setting it here has no effect on generated routes. |
isPublic |
bool |
false |
Turns off every auth guard on the route. @Public() sets it. |
You normally set the guard fields through the permission annotations rather than by hand.
Merge order
Section titled “Merge order”The generator merges, from least to most specific:
AppConfig.defaultRouteConfig@UseRouteConfigon the class- guards from
@RequireAuth,@Permissions,@AnyPermission,@Roles,@Public(class and method combined) - the annotation’s
config: @UseMiddlewarenames (class, then method)
merge()
Section titled “merge()”RouteConfig merge(RouteConfig? other)Returns a new config where scalar fields come from other when set and fall back to this; the permission, any-permission and role sets are unioned; middleware lists are concatenated. When other.isPublic is true, the result is public and all guards are cleared. A public config stays public through later layers unless one of them adds a guard itself. merge(null) returns this.
operator +
Section titled “operator +”Shorthand for merge: base + override.
copyWith()
Section titled “copyWith()”Returns a copy with the given fields replaced (no union, no public logic). Prefer merge.
hasAuthGuards
Section titled “hasAuthGuards”true when the config is not public and requires auth or lists any permission or role. When true, the route runs the authenticator and permission checks.
Example
@Controller('/auth')class AuthController { @Post('/login', config: RouteConfig( rateLimit: RateLimitConfig(requestsPerMinute: 10, burstSize: 5), contentTypes: {'application/json'}, maxBodyBytes: 16 * 1024, )) Future<Map<String, Object?>> login(LoginForm form, AuthService auth) => auth.login(form.email.value!, form.password.value!);
@Get('/report', config: RouteConfig(timeout: Duration(seconds: 60))) @Roles({'admin'}) Future<Report> report(ReportService reports) => reports.build();}See also: Rate limiting guide, Request lifecycle.
gisilaRoute
Section titled “gisilaRoute”Builds the Shelf handler for one route from its effective config and a typed handler. Generated attachToApp methods call it for every route.
Handler gisilaRoute({ required GisilaApp app, required RouteConfig config, required GisilaRouteHandler handler, String? routeId,})When to use: to register a hand-written route that still gets gisila’s per-route pipeline (guards, limits, timeout) without the generator, for example inside a custom ControllerAttacher.
| Parameter | Type | Default | Description |
|---|---|---|---|
app |
GisilaApp |
required | Supplies defaultRouteConfig, the authenticator, the permission resolver, trusted proxies and the rate-limit store. |
config |
RouteConfig |
required | Merged on top of app.config.defaultRouteConfig. |
handler |
GisilaRouteHandler |
required | Receives the request context and returns the response. |
routeId |
String? |
null |
'METHOD /path/<param>'. Names the route’s limiter in a shared rate-limit store and labels metrics with the path template. Without it, metrics label the route other. |
The per-route pipeline, outermost first: CORS, rate limit, content-type check, body cap, custom middleware, timeout, auth guard, handler. Guard failures answer 401 (unauthorized) or 403 (forbidden) directly.
app.registerController(attacher: (app, router, {prefix = ''}) { router.get( '$prefix/version', gisilaRoute( app: app, config: const RouteConfig(timeout: Duration(seconds: 1)), routeId: 'GET $prefix/version', handler: (ctx) => jsonResponse(body: {'version': '1.2.3'}), ), );});GisilaRouteHandler
Section titled “GisilaRouteHandler”typedef GisilaRouteHandler = FutureOr<Response> Function(RequestContext ctx);The typed handler gisilaRoute wraps. ctx.request is the request as it reached the handler.
guardRequest
Section titled “guardRequest”Runs authentication and permission checks against the GisilaApp attached to the request.
Future<GuardResult> guardRequest( Request request, { required bool requireAuth, Set<String> permissions = const {}, Set<String> anyPermission = const {}, Set<String> roles = const {},})When to use: in a hand-written Shelf handler mounted inside a GisilaApp that must enforce the same guards as generated routes. Generated code no longer calls it (guards run in gisilaRoute). It returns a RequestContext with principal set on success, or a ready 401/403 Response on failure. Outside a GisilaApp pipeline it fails closed with a 500 when any guard is requested.
Future<Response> adminStats(Request request) async { final result = await guardRequest(request, requireAuth: true, roles: {'admin'}); if (result is Response) return result; final ctx = result as RequestContext; return jsonResponse(body: {'user': ctx.principal!.id});}GuardResult
Section titled “GuardResult”typedef GuardResult = Object;Return type of guardRequest: a RequestContext or a Response.