Skip to main content

Call

Struct Call 

Source
pub struct Call { /* private fields */ }
Expand description

Per-request context: the single object a handler receives (Ktor-style).

A Call bundles the request method, URI, headers, captured path parameters, the (buffered) body, a handle to shared application state, and a per-call extension map. Handlers receive it either directly (|c: Call| async { ... }) or indirectly through extractors such as Path and Query, which read from the Call for you.

Read-only accessors (method, uri, path, header, query, param) borrow &self; body consumption (receive_bytes, receive_text) takes &mut self. The insert/get pair passes typed values between middleware and downstream handlers.

Construct one with Call::new in unit tests; in production the engine and the TestClient build it for you.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

let call = Call::new(
    Method::GET,
    "/search?q=rust".parse().unwrap(),
    HeaderMap::new(),
    Bytes::new(),
);
assert_eq!(call.method(), &Method::GET);
assert_eq!(call.path(), "/search");
assert_eq!(call.query("q").as_deref(), Some("rust"));

Implementations§

Source§

impl Call

Source

pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self

Construct a Call from already-parsed request parts (used by the engine and the test harness alike). Build a call whose body is already in memory.

The engine instead attaches the body as a stream, so a handler can read a large one incrementally — see Call::body_stream.

Source

pub fn method(&self) -> &Method

The request HTTP method.

Source

pub fn uri(&self) -> &Uri

The full request URI (path, query, and any authority).

Source

pub fn path(&self) -> &str

The request path, without the query string (e.g. /users/42).

Source

pub fn headers(&self) -> &HeaderMap

The full request header map.

Source

pub fn host(&self) -> Option<String>

The request’s host, without the port.

Reads the URI authority first, falling back to the Host header. Both spellings matter: HTTP/1.1 in the ordinary origin form carries the host in a header and nowhere else, while HTTP/2 removed that header in favour of the :authority pseudo-header, which lands in the URI. Anything that makes a decision about which site a request is for must consult both, or it silently stops matching on the protocol most clients now negotiate.

When a request carries both — an HTTP/2 peer that appends a stray host field beside :authority, or an HTTP/1.1 absolute-form target, where Host is still mandatory — the authority wins, as RFC 9113 §8.3.1 and RFC 9112 §3.2.2 both require.

use churust_core::Call;
use http::{HeaderMap, HeaderValue, Method, header::HOST};
use bytes::Bytes;

let mut headers = HeaderMap::new();
headers.insert(HOST, HeaderValue::from_static("example.com:8443"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.host().as_deref(), Some("example.com"));

// HTTP/2: no Host header, authority in the URI.
let h2 = Call::new(
    Method::GET,
    "https://example.com/".parse().unwrap(),
    HeaderMap::new(),
    Bytes::new(),
);
assert_eq!(h2.host().as_deref(), Some("example.com"));
Source

pub fn set_uri(&mut self, uri: Uri)

Replace the request URI.

For middleware that rewrites the target — a path normaliser, a rewrite rule. Note that routing has already happened by the time middleware runs, so this changes what handlers and extractors read, not which handler was selected.

Source

pub fn headers_mut(&mut self) -> &mut HeaderMap

Mutable access to the request headers.

Middleware that rewrites the request head needs this — a request-id layer that injects a header, or a proxy-header normaliser. Handlers see whatever the pipeline left here, so a middleware that edits headers is editing what every extractor downstream will read.

Source

pub fn header(&self, name: &str) -> Option<&str>

The value of header name as a UTF-8 string, or None if the header is absent or its value is not valid UTF-8. Header name matching is case-insensitive.

use churust_core::Call;
use http::{HeaderMap, Method, header::ACCEPT, HeaderValue};
use bytes::Bytes;

let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.header("accept"), Some("application/json"));
assert_eq!(call.header("x-missing"), None);
Source

pub fn query_string(&self) -> &str

The raw query string with the leading ? stripped, or "" if there is none. To deserialize the whole query into a struct, prefer the Query extractor.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

let call = Call::new(Method::GET, "/s?a=1&b=2".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.query_string(), "a=1&b=2");
Source

pub fn query(&self, key: &str) -> Option<String>

The first value for query key key, percent- and +-decoded, or None if the key is absent.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

let call = Call::new(Method::GET, "/s?q=hello+world".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.query("q").as_deref(), Some("hello world"));
assert_eq!(call.query("missing"), None);
Source

pub fn peer_addr(&self) -> Option<SocketAddr>

The address the connection came from.

None when the call did not come from the engine — a TestClient request, for instance, has no socket behind it.

This is the socket peer. Behind a reverse proxy it is the proxy, not the client: consult X-Forwarded-For only after checking this against the addresses you actually trust, since the header is client-supplied and trivially forged.

Source

pub fn cookie(&self, name: &str) -> Option<String>

The value of request cookie name, percent-decoded.

use churust_core::Call;
use http::{HeaderMap, HeaderValue, Method};
use bytes::Bytes;
let mut headers = HeaderMap::new();
headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; b=two"));
let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
assert_eq!(call.cookie("b").as_deref(), Some("two"));
assert_eq!(call.cookie("missing"), None);
Source

pub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>>

A shared handle to application state of type T, or None if no value of that type was registered with AppBuilder::state. For handler arguments, the State extractor is usually more convenient.

use churust_core::{Churust, Call, TestClient};
#[derive(Clone)]
struct AppName(&'static str);

let app = Churust::server()
    .state(AppName("churust"))
    .routing(|r| {
        r.get("/", |c: Call| async move {
            format!("app={}", c.state::<AppName>().unwrap().0)
        });
    })
    .build();
let res = TestClient::new(app).get("/").send().await;
assert_eq!(res.text(), "app=churust");
Source

pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)>

Iterate over the captured path parameters as (name, value) pairs, in capture order. Pairs come back in the order the route captured them.

Source

pub fn params(&self) -> &Params

The captured path parameters, in capture order.

Source

pub fn param_raw(&self, name: &str) -> Option<&str>

The raw, unparsed value of path parameter name, or None if the route has no such parameter. Use param to parse it into a typed value.

Source

pub fn param<T>(&self, name: &str) -> Result<T>
where T: FromStr, T::Err: Display,

Parse path parameter name into T.

Returns a 400 Bad Request Error if the parameter is missing or fails to parse.

use churust_core::{Churust, Call, TestClient};
let app = Churust::server()
    .routing(|r| {
        r.get("/users/{id}", |c: Call| async move {
            let id: u64 = c.param("id")?;
            Ok::<_, churust_core::Error>(format!("user {id}"))
        });
    })
    .build();
let res = TestClient::new(app).get("/users/42").send().await;
assert_eq!(res.text(), "user 42");
Source

pub async fn receive_bytes(&mut self) -> Bytes

Take the request body as raw Bytes, leaving the call’s body empty.

This consumes the body: a second call returns an empty buffer.

§Prefer try_receive_bytes

An empty return does not mean an empty body. The body now arrives as a stream, and this method has no error channel, so a read that fails — most importantly one that exceeds max_body_bytes — is reported as zero bytes. A handler built on this answers 200 with whatever an empty body produces, where the caller should have seen 413 Payload Too Large.

Use try_receive_bytes and let ? turn the failure into the right status. This method is kept for the case where the distinction genuinely does not matter.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("data"));
assert_eq!(&call.receive_bytes().await[..], b"data");
assert!(call.receive_bytes().await.is_empty()); // already consumed
Source

pub async fn try_receive_bytes(&mut self) -> Result<Bytes>

Read the whole body, surfacing the size limit as an error.

receive_bytes exists for callers that cannot report failure and yields an empty buffer instead; extractors should prefer this so an oversized body becomes 413 rather than a confusing deserialization error.

Source

pub async fn try_receive_bytes_within(&mut self, max: usize) -> Result<Bytes>

Collect the body, refusing it the moment it exceeds max.

The distinction from checking afterwards is memory, not status. A body gathered first and measured second has already been allocated: a 16 MiB upload against a 64 KiB route cap peaked above 32 MiB — BytesMut doubles — before anything refused it, and N concurrent requests multiplied that. The 413 was correct and arrived far too late to be the protection it looked like.

Errors with 413 Payload Too Large as soon as the accumulated length crosses max, so the peak is bounded by the cap rather than by what the client chose to send.

Source

pub fn body_stream(&mut self) -> Option<BodyStream>

Take the body as a stream, without buffering it.

This is how a large upload is processed without holding it in memory. Returns None if the body has already been consumed. A body that arrived buffered — from Call::new or a test client — yields a single-chunk stream, so a handler need not care which it got.

The server-wide max_body_bytes still applies: exceeding it surfaces as an error item in the stream rather than a 413, because the response has usually begun by then.

Source

pub async fn receive_text(&mut self) -> Result<String>

Take the request body decoded as UTF-8, consuming it.

Returns a 400 Bad Request Error if the body is not valid UTF-8.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;
let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("ping"));
assert_eq!(call.receive_text().await.unwrap(), "ping");
Source

pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T)

Insert a per-call typed value into the call’s extension map, keyed by its type. Typically used by a middleware to attach context (e.g. an authenticated principal) that a downstream extractor or handler reads via get. Inserting a second value of the same type replaces the first.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

#[derive(Clone, PartialEq, Debug)]
struct UserId(u32);

let mut call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
call.insert(UserId(7));
assert_eq!(call.get::<UserId>(), Some(UserId(7)));
Source

pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T>

Get a clone of the per-call value of type T previously stored with insert, or None if none was stored.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.get::<u32>(), None);
Source

pub fn respond_text(&self, body: impl Into<String>) -> Response

Build a 200 OK text/plain Response — a shorthand for Response::text.

use churust_core::Call;
use http::{HeaderMap, Method};
use bytes::Bytes;

let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
let res = call.respond_text("ok");
assert_eq!(res.body.as_slice(), Some(&b"ok"[..]));
Source

pub fn respond_status(&self, status: StatusCode) -> Response

Build an empty-bodied Response with the given status — a shorthand for Response::new.

use churust_core::Call;
use http::{HeaderMap, Method, StatusCode};
use bytes::Bytes;

let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
assert_eq!(call.respond_status(StatusCode::ACCEPTED).status, StatusCode::ACCEPTED);

Trait Implementations§

Source§

impl Debug for Call

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromCall for Call

The whole Call as the final argument (Ktor call-style base case). NOTE: deliberately NOT FromCallParts — that would conflict with the blanket impl above.

Source§

fn from_call<'async_trait>( call: Call, ) -> Pin<Box<dyn Future<Output = Result<Self>> + Send + 'async_trait>>
where Self: 'async_trait,

Build Self by consuming the call, or return an Error (which the handler renders as the response).

Auto Trait Implementations§

§

impl !Freeze for Call

§

impl !RefUnwindSafe for Call

§

impl !UnwindSafe for Call

§

impl Send for Call

§

impl Sync for Call

§

impl Unpin for Call

§

impl UnsafeUnpin for Call

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