pub struct Router { /* private fields */ }Expand description
The route builder. Accumulates entries by value through a fluent chain
and converts to a real axum::Router via Router::into_axum_router.
Every method here consumes self and returns a new value rather than
mutating in place - a call left unchained (Router::new().get(...);)
silently drops the route, hence #[must_use].
Implementations§
Source§impl Router
impl Router
pub fn new() -> Self
pub fn get<H, T>(self, path: &str, handler: H) -> Self
pub fn post<H, T>(self, path: &str, handler: H) -> Self
pub fn put<H, T>(self, path: &str, handler: H) -> Self
pub fn patch<H, T>(self, path: &str, handler: H) -> Self
pub fn delete<H, T>(self, path: &str, handler: H) -> Self
Sourcepub fn name(self, name: &str) -> Self
pub fn name(self, name: &str) -> Self
Names the most recently added route (mirrors Laravel’s
Route::get(...)->name(...), which also applies to the immediately
preceding registration).
Unlike Laravel’s Route::name('prefix.')->group(...), chaining
.name(...) directly after .group(...) does not prefix every
route inside the group - it only names the group’s last route, same
as anywhere else. Name routes inside the build closure instead.
A no-op (not an error) if no route has been added yet.
Sourcepub fn group<F>(self, prefix: &str, build: F) -> Self
pub fn group<F>(self, prefix: &str, build: F) -> Self
Nests a group of routes under a shared path prefix. build receives
a fresh Router and returns it with its own routes added - plain
value-passing, no implicit/global registration.
Any .middleware(...) calls made inside build’s closure are
scoped to just this group’s routes (Laravel’s
Route::middleware(...)->group(...)) - baked into each entry’s
MethodRouter before it’s merged into self, so they can’t leak
out and affect sibling routes registered outside the group. This is
a different mechanism from top-level .middleware() (which applies
to every entry in self, including ones added by .group()) -
the two compose: a route inside a group is wrapped by the group’s
own middleware first, then by the parent router’s global middleware.
Calling .with_sessions(...) inside build’s closure isn’t
meaningful anyway: sessions are a single layer for the whole app,
applied only by the outermost Router::into_axum_router() call -
and since with_sessions is async while build: FnOnce(Router) -> Router isn’t, r.with_sessions(pool, secure) inside the closure
produces a Future, not a Router, so it doesn’t even type-check
as a chain continuation there.
Sourcepub fn merge(self, prefix: &str, other: Router) -> Self
pub fn merge(self, prefix: &str, other: Router) -> Self
Merges other’s routes into self under prefix, keeping each
router’s own top-level .middleware(...) stack fully independent -
unlike Router::group (which deliberately shares self’s
top-level middleware with whatever its closure registers, see that
method’s own doc comment), neither router’s global middleware leaks
onto the other’s routes. Laravel’s own routes/web.php/
routes/api.php split works this way implicitly (the framework’s
own bootstrap puts each file under its own middleware group -
web/api - that never shares state with the other); this is the
explicit equivalent for two Router values built independently and
combined by hand, e.g. a web-style router’s own CSRF middleware
must never apply to an api-style router’s routes, and the api
router’s own rate-limiting middleware must never apply back to the
web router’s routes either. Use .group(...) when nested routes
should inherit the parent’s top-level middleware (the common
case - an auth-gated section of the same route file); use .merge
only when combining two genuinely separate route trees whose
middleware stacks must stay isolated.
other’s own top-level middleware is baked into its entries
immediately here - the same “wrap now, not deferred to
into_axum_router()” mechanism .group(...) already uses for a
closure’s sub-router - so calling .middleware(...) on other
after passing it to this method has no effect; register all of
other’s own middleware before merging it in. other’s own
session_layer (if .with_sessions(...) was ever called on it) is
discarded, not merged - same stance .group(...)’s own doc comment
already takes: sessions are a single layer for the whole app,
applied only by the outermost Router::into_axum_router() call, so
call .with_sessions(...) only on the final, fully-merged router.
Sourcepub fn plugin<P: Plugin>(self, plugin: P) -> Self
pub fn plugin<P: Plugin>(self, plugin: P) -> Self
Merges plugin’s Plugin::routes() into self, unprefixed - a
plugin’s routes are already complete, absolute paths
(/__larust_wire/runtime.js), unlike a real .merge(prefix, ...)
call (e.g. mounting routes/api.rs under app.config().api_prefix)
- nothing here should be textually prepended.
Deliberately NOT self.merge("", plugin.routes()) - that was
this method’s original implementation, and it was a real bug, not
just a missed optimization: .merge() marks every incoming entry
immune_to_parent_middleware, correct for its own purpose
(isolating routes/api.rs from routes/web.rs’s own middleware
stack) but wrong here - a plugin’s routes are ordinary, first-class
routes on this app, contributed by a crate instead of hand-
written, and should inherit whatever this router’s own
.middleware(...) calls apply, exactly as a hand-written
.post(...) call in their place would. The bug was concrete, not
theoretical: WirePlugin’s /__larust_wire/{component_id} route
silently stopped being covered by the app’s own trailing
.middleware(csrf::verify) call the moment demo/scaffold.rs
switched from a hand-written .post(...) registration to
.plugin(WirePlugin) - a real CSRF-protection regression, caught
only by re-reading merge’s own body afterward, not by any test
(nothing exercised cross-origin request rejection specifically).
This method now pushes each entry with immune_to_parent_middleware: false instead, so it composes the same way manual registration
always did. A plugin’s own routes()-internal .middleware(...)
calls (if it ever registers any) are unaffected either way - those
are baked into each entry’s method_router immediately, the same
other.middlewares fold .merge() itself still uses.
Call this only at the top level of a Router chain, never inside a
Router::group closure - .group() prefixes and re-wraps every
entry in its closure’s sub-router uniformly, with no awareness that
some of them arrived via a nested .plugin()/.merge() call, so
nesting it would double-prefix a plugin’s already-absolute paths
and wrongly apply the group’s own middleware to them (this part is
unrelated to immune_to_parent_middleware - .group()’s own fold
never checks that flag at all). See docs/ARCHITECTURE.md’s
“Plugins” section.
Sourcepub fn resource<HIndex, TIndex, HCreate, TCreate, HStore, TStore, HShow, TShow, HEdit, TEdit, HUpdate, TUpdate, HDestroy, TDestroy>(
self,
prefix: &str,
param: &str,
index: HIndex,
create: HCreate,
store: HStore,
show: HShow,
edit: HEdit,
update: HUpdate,
destroy: HDestroy,
) -> Selfwhere
HIndex: Handler<TIndex, ()>,
TIndex: 'static,
HCreate: Handler<TCreate, ()>,
TCreate: 'static,
HStore: Handler<TStore, ()>,
TStore: 'static,
HShow: Handler<TShow, ()>,
TShow: 'static,
HEdit: Handler<TEdit, ()>,
TEdit: 'static,
HUpdate: Handler<TUpdate, ()>,
TUpdate: 'static,
HDestroy: Handler<TDestroy, ()>,
TDestroy: 'static,
pub fn resource<HIndex, TIndex, HCreate, TCreate, HStore, TStore, HShow, TShow, HEdit, TEdit, HUpdate, TUpdate, HDestroy, TDestroy>(
self,
prefix: &str,
param: &str,
index: HIndex,
create: HCreate,
store: HStore,
show: HShow,
edit: HEdit,
update: HUpdate,
destroy: HDestroy,
) -> Selfwhere
HIndex: Handler<TIndex, ()>,
TIndex: 'static,
HCreate: Handler<TCreate, ()>,
TCreate: 'static,
HStore: Handler<TStore, ()>,
TStore: 'static,
HShow: Handler<TShow, ()>,
TShow: 'static,
HEdit: Handler<TEdit, ()>,
TEdit: 'static,
HUpdate: Handler<TUpdate, ()>,
TUpdate: 'static,
HDestroy: Handler<TDestroy, ()>,
TDestroy: 'static,
Registers all 7 RESTful routes for a resource in one call - Laravel’s
Route::resource('posts', PostController::class). prefix is the
resource’s bare name, matching Laravel’s own calling convention -
no leading slash ("posts", not "/posts"; the path gets one
prepended automatically, the route names must not have one at
all, and prefix is used for both). param is the path-parameter
name used for show/edit/update/destroy ("post"), matching
whatever #[derive(Model)]’s route model binding expects for the
bound type (its snake_case name by default, or #[route_key(...)]’s
override) - param is taken explicitly rather than singularized
from prefix ("categories" → "category", not "categorie"),
matching this codebase’s existing “explicit string, never inferred”
stance for anything with this shape (see #[belongs_to_many(...)]’s
related_pivot_key).
Generates, in Laravel’s own order and naming convention:
| Method | Path | Name |
|---|---|---|
| GET | /{prefix} | {prefix}.index |
| GET | /{prefix}/create | {prefix}.create |
| POST | /{prefix} | {prefix}.store |
| GET | /{prefix}/{{param}} | {prefix}.show |
| GET | /{prefix}/{{param}}/edit | {prefix}.edit |
| PUT | /{prefix}/{{param}} | {prefix}.update |
| DELETE | /{prefix}/{{param}} | {prefix}.destroy |
Built from this struct’s own .get/.post/.put/.delete/.name -
sugar over them, not a separate registration path - so it composes
with .middleware(...)/.group(...) exactly like a hand-written
sequence of those calls would (e.g. wrapping a whole resource behind
require_auth).
Sourcepub fn routes(&self) -> Vec<RouteInfo>
pub fn routes(&self) -> Vec<RouteInfo>
Snapshot of currently registered routes, for introspection (e.g.
xr route:list).
Sourcepub fn middleware<L>(self, layer: L) -> Self
pub fn middleware<L>(self, layer: L) -> Self
Applies middleware to every route registered on self (Laravel’s
global middleware stack) - including routes added via .group(...),
since a group’s entries are merged into self.entries before this
router is converted. For middleware scoped to only some routes,
call .middleware(...) inside a .group(...) closure instead - see
that method’s doc comment. Accepts any tower::Layer, matching
axum::Router::layer exactly - wrap an extractor-based handler with
axum::middleware::from_fn(handler) first (needed because
from_fn’s own generic dispatch over arbitrary leading extractor
arguments can’t be re-expressed through a narrower wrapper signature
here).
The first middleware registered runs first on an incoming request -
call order is execution order, matching Laravel’s middleware-array
semantics (not axum’s own raw .layer() semantics, where the last
.layer() call ends up outermost/runs-first; into_axum_router()
applies these in reverse specifically to invert that so callers of
this method don’t have to think about it).
Session data (from .with_sessions()) is available to every
middleware registered here regardless of call order relative to
.with_sessions() - that layer is always outermost.
Sourcepub async fn with_sessions(
self,
pool: &AnyPool,
secure: bool,
) -> Result<Self, AppError>
pub async fn with_sessions( self, pool: &AnyPool, secure: bool, ) -> Result<Self, AppError>
Enables cookie-based sessions, backed by either an AnySessionStore
over pool or a FileSessionStore (see larust_http::session’s
own doc comment - Config::session_driver picks which one; session
data survives a process restart either way, unlike an in-memory
store). Always applied outermost, so session data is available to
every other middleware registered via .middleware(...) regardless
of call order.
Async because preparing the store does real I/O (the database
backend’s idempotent CREATE TABLE IF NOT EXISTS; the file
backend’s create_dir_all) - call this only once a database
connection actually exists (even when the file backend ends up
being the one actually used - which backend is active isn’t known
until session_layer itself resolves Config::session_driver),
and prefer calling it after checking for a route:list-style
early exit, since introspecting registered routes doesn’t need a
working database at all.
secure sets the session cookie’s Secure attribute - pass
app.config().session_secure_cookie (true unless a
SESSION_SECURE_COOKIE=false override says otherwise) rather than a
literal, so local dev on a custom hostname (e.g. a .test domain)
can opt out without a code change. See
larust_http::session::session_layer’s doc comment for why this
matters.
Sourcepub fn into_axum_router(self) -> Router
pub fn into_axum_router(self) -> Router
Converts to a real axum::Router and publishes named routes to the
process-wide registry that resolve_route_name reads from.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Router
impl !Send for Router
impl !Sync for Router
impl !UnwindSafe for Router
impl Freeze for Router
impl Unpin for Router
impl UnsafeUnpin for Router
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more