Skip to main content

Router

Struct Router 

Source
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

Source

pub fn new() -> Self

Source

pub fn get<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, ()>, T: 'static,

Source

pub fn post<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, ()>, T: 'static,

Source

pub fn put<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, ()>, T: 'static,

Source

pub fn patch<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, ()>, T: 'static,

Source

pub fn delete<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, ()>, T: 'static,

Source

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.

Source

pub fn group<F>(self, prefix: &str, build: F) -> Self
where F: FnOnce(Router) -> Router,

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.

Source

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.

Source

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.

Source

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, ) -> Self
where 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:

MethodPathName
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).

Source

pub fn routes(&self) -> Vec<RouteInfo>

Snapshot of currently registered routes, for introspection (e.g. xr route:list).

Source

pub fn middleware<L>(self, layer: L) -> Self
where L: Layer<Route> + Clone + Send + 'static, L::Service: Service<Request> + Clone + Send + 'static, <L::Service as Service<Request>>::Response: IntoResponse + 'static, <L::Service as Service<Request>>::Error: Into<Infallible> + 'static, <L::Service as Service<Request>>::Future: Send + 'static,

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.

Source

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.

Source

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§

Source§

impl Default for Router

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more