Skip to main content

RouteBuilder

Struct RouteBuilder 

Source
pub struct RouteBuilder<'r> { /* private fields */ }
Expand description

The route-definition DSL handed to the closure in AppBuilder::routing.

Register handlers with the per-method helpers (get, post, put, delete) or the generic method. Group related routes under a common prefix with route, which nests cleanly. Each handler may be an extractor closure or anything implementing Handler.

use churust_core::{Churust, Call, TestClient};
let app = Churust::server()
    .routing(|r| {
        r.get("/", |_c: Call| async { "home" });
        r.route("/api", |r| {
            r.get("/health", |_c: Call| async { "ok" });
        });
    })
    .build();
let res = TestClient::new(app).get("/api/health").send().await;
assert_eq!(res.text(), "ok");

Implementations§

Source§

impl<'r> RouteBuilder<'r>

Source

pub fn intercept<M: Middleware>(&mut self, mw: M) -> &mut Self

Apply mw to every route registered in this scope after this call, including those in nested scopes.

This is v1 design §6’s scope-local interception, and the equivalent of scope-local interception. Ordering is deliberate: a route registered before the intercept call is not covered by it, so the reading order of the block matches the behaviour.

use churust_core::{Call, Churust, Middleware, Next, Response, TestClient};
use async_trait::async_trait;
use http::StatusCode;

struct RequireKey;
#[async_trait]
impl Middleware for RequireKey {
    async fn handle(&self, call: Call, next: Next) -> Response {
        if call.header("x-key").is_some() {
            next.run(call).await
        } else {
            Response::new(StatusCode::UNAUTHORIZED)
        }
    }
}

let app = Churust::server()
    .routing(|r| {
        r.get("/open", |_c: Call| async { "open" });
        r.route("/admin", |r| {
            r.intercept(RequireKey);
            r.get("/panel", |_c: Call| async { "panel" });
        });
    })
    .build();

let client = TestClient::new(app);
assert_eq!(client.get("/open").send().await.status(), StatusCode::OK);
assert_eq!(
    client.get("/admin/panel").send().await.status(),
    StatusCode::UNAUTHORIZED
);
Source

pub fn method<Marker, H>( &mut self, method: Method, path: &str, handler: H, ) -> &mut Self
where H: IntoHandler<Marker>,

Register a handler for method at path. Accepts both extractor closures (via the HandlerFn family) and anything already implementing Handler — including a pre-boxed BoxHandler — through IntoHandler.

Source

pub fn guard(&mut self, g: BoxGuard) -> &mut Self

Attach a Guard to the route just registered.

Routes may share a (method, path) when guards distinguish them; the first whose guards all pass serves the request, in registration order, so an unguarded route registered last is the fallback. A request that matches no candidate is a 404.

use churust_core::{guard, Call, Churust, TestClient};
let app = Churust::server()
    .routing(|r| {
        r.get("/", |_c: Call| async { "beta" })
            .guard(guard::header("x-beta", "1"));
        r.get("/", |_c: Call| async { "stable" });
    })
    .build();
let client = TestClient::new(app);
assert_eq!(client.get("/").header("x-beta", "1").send().await.text(), "beta");
assert_eq!(client.get("/").send().await.text(), "stable");
§Panics

Panics if no route has been registered on this builder yet — there would be nothing for the guard to apply to.

Source

pub fn max_body_bytes(&mut self, n: usize) -> &mut Self

Cap the request body for the route just registered.

The server-wide max_body_bytes still bounds what is read off the socket; this tightens it for one route, which is what lets an upload endpoint be generous while the rest of the API stays strict. Body extractors (Json<T>, Form<T>) enforce it and answer 413.

use churust_core::{Call, Churust, Form, TestClient};
use http::StatusCode;
use serde::Deserialize;
#[derive(Deserialize)]
struct Note { text: String }

let app = Churust::server()
    .routing(|r| {
        r.post("/tiny", |Form(n): Form<Note>| async move { n.text })
            .max_body_bytes(8);
    })
    .build();

let res = TestClient::new(app)
    .post("/tiny")
    .header("content-type", "application/x-www-form-urlencoded")
    .body("text=this-is-far-too-long")
    .send()
    .await;
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
§Panics

Panics if no route has been registered on this builder yet.

Source

pub fn get<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where H: IntoHandler<Marker>,

Register a GET handler at path. Returns &mut Self for chaining.

Source

pub fn post<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where H: IntoHandler<Marker>,

Register a POST handler at path. Returns &mut Self for chaining.

Source

pub fn put<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where H: IntoHandler<Marker>,

Register a PUT handler at path. Returns &mut Self for chaining.

Source

pub fn delete<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where H: IntoHandler<Marker>,

Register a DELETE handler at path. Returns &mut Self for chaining.

Source

pub fn route( &mut self, path: &str, f: impl FnOnce(&mut RouteBuilder<'_>), ) -> &mut Self

Open a nested scope: every route registered inside f has path prepended to its pattern. Scopes nest arbitrarily, so prefixes compose. Returns &mut Self for chaining.

Auto Trait Implementations§

§

impl<'r> !RefUnwindSafe for RouteBuilder<'r>

§

impl<'r> !UnwindSafe for RouteBuilder<'r>

§

impl<'r> Freeze for RouteBuilder<'r>

§

impl<'r> Send for RouteBuilder<'r>

§

impl<'r> Sync for RouteBuilder<'r>

§

impl<'r> Unpin for RouteBuilder<'r>

§

impl<'r> UnsafeUnpin for RouteBuilder<'r>

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<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> 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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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