Skip to main content

churust_core/
call.rs

1//! The per-request [`Call`] context — the single object every handler receives.
2
3use crate::error::{Error, Result};
4use crate::response::Response;
5use crate::state::StateMap;
6use bytes::Bytes;
7use http::{HeaderMap, Method, StatusCode, Uri};
8use std::sync::Arc;
9/// Captured path parameters, in the order the route captured them.
10///
11/// Order matters: `Path<(A, B)>` destructures positionally, so a `HashMap`
12/// would make `/users/{id}/posts/{post}` ambiguous.
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct Params(Vec<(String, String)>);
15
16impl Params {
17    /// An empty set.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Add or replace `name`.
23    pub fn insert(&mut self, name: String, value: String) {
24        match self.0.iter_mut().find(|(k, _)| *k == name) {
25            Some(slot) => slot.1 = value,
26            None => self.0.push((name, value)),
27        }
28    }
29
30    /// Drop `name`, if present.
31    pub fn remove(&mut self, name: &str) {
32        self.0.retain(|(k, _)| k != name);
33    }
34
35    /// The value for `name`.
36    pub fn get(&self, name: &str) -> Option<&str> {
37        self.0
38            .iter()
39            .find(|(k, _)| k == name)
40            .map(|(_, v)| v.as_str())
41    }
42
43    /// Pairs in capture order.
44    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
45        self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
46    }
47
48    /// Whether `name` was captured.
49    pub fn contains_key(&self, name: &str) -> bool {
50        self.get(name).is_some()
51    }
52
53    /// The value at `index`, in capture order.
54    pub fn nth(&self, index: usize) -> Option<&str> {
55        self.0.get(index).map(|(_, v)| v.as_str())
56    }
57
58    /// How many were captured.
59    pub fn len(&self) -> usize {
60        self.0.len()
61    }
62
63    /// Whether none were captured.
64    pub fn is_empty(&self) -> bool {
65        self.0.is_empty()
66    }
67
68    /// Discard every capture.
69    pub fn clear(&mut self) {
70        self.0.clear();
71    }
72}
73
74/// The address the connection came from, seeded by the engine.
75///
76/// A newtype rather than a bare `SocketAddr` so it can live in the call's typed
77/// extension map without colliding with anything else of that type.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct PeerAddr(pub std::net::SocketAddr);
80
81/// A request body still arriving, as a stream of chunks.
82pub type BodyStream =
83    std::pin::Pin<Box<dyn futures_util::Stream<Item = Result<Bytes>> + Send + 'static>>;
84
85/// Where a request body currently is: already in memory, or still arriving.
86enum RequestBody {
87    Buffered(Bytes),
88    /// Behind a mutex so `Call` stays `Sync`. A boxed stream is `Send` but not
89    /// `Sync`, and handler bounds require `Sync`; the mutex is uncontended
90    /// because the stream is taken exactly once.
91    Stream(std::sync::Mutex<Option<BodyStream>>),
92}
93
94impl std::fmt::Debug for RequestBody {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        match self {
97            RequestBody::Buffered(b) => write!(f, "Buffered({} bytes)", b.len()),
98            RequestBody::Stream(_) => f.write_str("Stream"),
99        }
100    }
101}
102
103/// Per-request context: the single object a handler receives (Ktor-style).
104///
105/// A `Call` bundles the request method, URI, headers, captured path
106/// parameters, the (buffered) body, a handle to shared application
107/// [state](crate::StateMap), and a per-call extension map. Handlers receive it
108/// either directly (`|c: Call| async { ... }`) or indirectly through
109/// [extractors](crate::extract) such as [`Path`](crate::Path) and
110/// [`Query`](crate::Query), which read from the `Call` for you.
111///
112/// Read-only accessors ([`method`](Call::method), [`uri`](Call::uri),
113/// [`path`](Call::path), [`header`](Call::header), [`query`](Call::query),
114/// [`param`](Call::param)) borrow `&self`; body consumption
115/// ([`receive_bytes`](Call::receive_bytes), [`receive_text`](Call::receive_text))
116/// takes `&mut self`. The [`insert`](Call::insert)/[`get`](Call::get) pair
117/// passes typed values between middleware and downstream handlers.
118///
119/// Construct one with [`Call::new`] in unit tests; in production the engine and
120/// the [`TestClient`](crate::TestClient) build it for you.
121///
122/// ```
123/// use churust_core::Call;
124/// use http::{HeaderMap, Method};
125/// use bytes::Bytes;
126///
127/// let call = Call::new(
128///     Method::GET,
129///     "/search?q=rust".parse().unwrap(),
130///     HeaderMap::new(),
131///     Bytes::new(),
132/// );
133/// assert_eq!(call.method(), &Method::GET);
134/// assert_eq!(call.path(), "/search");
135/// assert_eq!(call.query("q").as_deref(), Some("rust"));
136/// ```
137#[derive(Debug)]
138pub struct Call {
139    method: Method,
140    uri: Uri,
141    headers: HeaderMap,
142    params: Params,
143    body: RequestBody,
144    state: Arc<StateMap>,
145    extensions: http::Extensions,
146}
147
148impl Call {
149    /// Construct a Call from already-parsed request parts (used by the engine
150    /// and the test harness alike).
151    /// Build a call whose body is already in memory.
152    ///
153    /// The engine instead attaches the body as a stream, so a handler can read
154    /// a large one incrementally — see [`Call::body_stream`].
155    pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self {
156        Self {
157            method,
158            uri,
159            headers,
160            params: Params::new(),
161            body: RequestBody::Buffered(body),
162            state: Arc::new(StateMap::default()),
163            extensions: http::Extensions::new(),
164        }
165    }
166
167    /// The request HTTP method.
168    pub fn method(&self) -> &Method {
169        &self.method
170    }
171    /// The full request URI (path, query, and any authority).
172    pub fn uri(&self) -> &Uri {
173        &self.uri
174    }
175    /// The request path, without the query string (e.g. `/users/42`).
176    pub fn path(&self) -> &str {
177        self.uri.path()
178    }
179    /// The full request header map.
180    pub fn headers(&self) -> &HeaderMap {
181        &self.headers
182    }
183
184    /// The request's host, without the port.
185    ///
186    /// Reads the URI authority first, falling back to the `Host` header. Both
187    /// spellings matter: HTTP/1.1 in the ordinary origin form carries the host
188    /// in a header and nowhere else, while HTTP/2 removed that header in favour
189    /// of the `:authority` pseudo-header, which lands in the URI. Anything that
190    /// makes a decision about *which site* a request is for must consult both,
191    /// or it silently stops matching on the protocol most clients now
192    /// negotiate.
193    ///
194    /// When a request carries both — an HTTP/2 peer that appends a stray `host`
195    /// field beside `:authority`, or an HTTP/1.1 absolute-form target, where
196    /// `Host` is still mandatory — the authority wins, as RFC 9113 §8.3.1 and
197    /// RFC 9112 §3.2.2 both require.
198    ///
199    /// ```
200    /// use churust_core::Call;
201    /// use http::{HeaderMap, HeaderValue, Method, header::HOST};
202    /// use bytes::Bytes;
203    ///
204    /// let mut headers = HeaderMap::new();
205    /// headers.insert(HOST, HeaderValue::from_static("example.com:8443"));
206    /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
207    /// assert_eq!(call.host().as_deref(), Some("example.com"));
208    ///
209    /// // HTTP/2: no Host header, authority in the URI.
210    /// let h2 = Call::new(
211    ///     Method::GET,
212    ///     "https://example.com/".parse().unwrap(),
213    ///     HeaderMap::new(),
214    ///     Bytes::new(),
215    /// );
216    /// assert_eq!(h2.host().as_deref(), Some("example.com"));
217    /// ```
218    pub fn host(&self) -> Option<String> {
219        // The authority is consulted first and the header only when the URI has
220        // none. Reading the header first meant a request that carried both was
221        // resolved against the one the wire protocol does *not* treat as the
222        // target: hyper builds the URI authority out of `:authority` alone, and
223        // h2 forwards an ordinary `host` field untouched because it is not one
224        // of the connection-specific fields it rejects, so an HTTP/2 peer can
225        // send `:authority: www.example.com` beside `host: admin.example.com`
226        // and an HTTP/1.1 peer can do the same with an absolute-form target.
227        // Both RFC 9113 §8.3.1 and RFC 9112 §3.2.2 settle that the same way —
228        // the authority is the target and the header is to be ignored — and
229        // agreeing with them is what keeps an intermediary that routed or
230        // authorized on the authority and the origin behind it from resolving
231        // one request to two different sites. On the origin-form HTTP/1.1
232        // request, which is the overwhelmingly common shape, the URI has no
233        // authority and nothing changes: the header is still the only signal.
234        //
235        // A disagreement is not refused outright, only decided. Answering `400`
236        // would be within the letter of RFC 9113, but the reading above already
237        // makes the stray field inert, and rejecting would turn a request that
238        // a lenient intermediary produced by accident into an outage for a
239        // caller who did nothing wrong.
240        let raw = self
241            .uri
242            .authority()
243            .map(|a| a.as_str().to_string())
244            .or_else(|| self.header(http::header::HOST.as_str()).map(str::to_string))?;
245        // Strip any userinfo first.
246        let after_at = raw.rsplit('@').next().unwrap_or(&raw);
247        // An IPv6 literal is bracketed and full of colons, so the port cannot
248        // be found by splitting on `:` — that yielded `"[2001"` and made every
249        // host comparison fail for v6. The brackets are authority syntax, so
250        // they come off with the port.
251        let host = match after_at.strip_prefix('[') {
252            Some(rest) => rest.split(']').next().unwrap_or(rest),
253            None => after_at.split(':').next().unwrap_or(after_at),
254        };
255        (!host.is_empty()).then(|| host.to_string())
256    }
257
258    /// Replace the request URI.
259    ///
260    /// For middleware that rewrites the target — a path normaliser, a rewrite
261    /// rule. Note that routing has already happened by the time middleware
262    /// runs, so this changes what handlers and extractors *read*, not which
263    /// handler was selected.
264    pub fn set_uri(&mut self, uri: Uri) {
265        self.uri = uri;
266    }
267
268    /// Mutable access to the request headers.
269    ///
270    /// Middleware that rewrites the request head needs this — a request-id
271    /// layer that injects a header, or a proxy-header normaliser. Handlers see
272    /// whatever the pipeline left here, so a middleware that edits headers is
273    /// editing what every extractor downstream will read.
274    pub fn headers_mut(&mut self) -> &mut HeaderMap {
275        &mut self.headers
276    }
277
278    /// The value of header `name` as a UTF-8 string, or `None` if the header is
279    /// absent or its value is not valid UTF-8. Header name matching is
280    /// case-insensitive.
281    ///
282    /// ```
283    /// use churust_core::Call;
284    /// use http::{HeaderMap, Method, header::ACCEPT, HeaderValue};
285    /// use bytes::Bytes;
286    ///
287    /// let mut headers = HeaderMap::new();
288    /// headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
289    /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
290    /// assert_eq!(call.header("accept"), Some("application/json"));
291    /// assert_eq!(call.header("x-missing"), None);
292    /// ```
293    pub fn header(&self, name: &str) -> Option<&str> {
294        self.headers.get(name).and_then(|v| v.to_str().ok())
295    }
296
297    /// The raw query string with the leading `?` stripped, or `""` if there is
298    /// none. To deserialize the whole query into a struct, prefer the
299    /// [`Query`](crate::Query) extractor.
300    ///
301    /// ```
302    /// use churust_core::Call;
303    /// use http::{HeaderMap, Method};
304    /// use bytes::Bytes;
305    ///
306    /// let call = Call::new(Method::GET, "/s?a=1&b=2".parse().unwrap(), HeaderMap::new(), Bytes::new());
307    /// assert_eq!(call.query_string(), "a=1&b=2");
308    /// ```
309    pub fn query_string(&self) -> &str {
310        self.uri.query().unwrap_or("")
311    }
312
313    /// The first value for query key `key`, percent- and `+`-decoded, or `None`
314    /// if the key is absent.
315    ///
316    /// ```
317    /// use churust_core::Call;
318    /// use http::{HeaderMap, Method};
319    /// use bytes::Bytes;
320    ///
321    /// let call = Call::new(Method::GET, "/s?q=hello+world".parse().unwrap(), HeaderMap::new(), Bytes::new());
322    /// assert_eq!(call.query("q").as_deref(), Some("hello world"));
323    /// assert_eq!(call.query("missing"), None);
324    /// ```
325    pub fn query(&self, key: &str) -> Option<String> {
326        form_urlencoded_first(self.query_string(), key)
327    }
328
329    /// Set by the router after a successful match.
330    pub(crate) fn set_params(&mut self, params: Params) {
331        self.params = params;
332    }
333
334    /// Injected by `App::process` before the pipeline runs.
335    pub(crate) fn set_state(&mut self, state: Arc<StateMap>) {
336        self.state = state;
337    }
338
339    /// The address the connection came from.
340    ///
341    /// `None` when the call did not come from the engine — a `TestClient`
342    /// request, for instance, has no socket behind it.
343    ///
344    /// This is the *socket* peer. Behind a reverse proxy it is the proxy, not
345    /// the client: consult `X-Forwarded-For` only after checking this against
346    /// the addresses you actually trust, since the header is client-supplied
347    /// and trivially forged.
348    pub fn peer_addr(&self) -> Option<std::net::SocketAddr> {
349        self.get::<PeerAddr>().map(|p| p.0)
350    }
351
352    /// The value of request cookie `name`, percent-decoded.
353    ///
354    /// ```
355    /// use churust_core::Call;
356    /// use http::{HeaderMap, HeaderValue, Method};
357    /// use bytes::Bytes;
358    /// let mut headers = HeaderMap::new();
359    /// headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; b=two"));
360    /// let call = Call::new(Method::GET, "/".parse().unwrap(), headers, Bytes::new());
361    /// assert_eq!(call.cookie("b").as_deref(), Some("two"));
362    /// assert_eq!(call.cookie("missing"), None);
363    /// ```
364    pub fn cookie(&self, name: &str) -> Option<String> {
365        // Every `Cookie` field, not just the first. HTTP/2 permits a client to
366        // send cookie crumbs as several separate header fields (RFC 9113
367        // §8.2.3), and an HTTP/1.1 client may send more than one `Cookie` line
368        // too. Reading only the first meant a session cookie that happened to
369        // land in the second field was invisible, so every request looked
370        // freshly anonymous — a silent logout on each request.
371        self.headers
372            .get_all(http::header::COOKIE)
373            .iter()
374            .filter_map(|v| v.to_str().ok())
375            .find_map(|raw| crate::cookie::find(raw, name))
376            .map(crate::cookie::decode)
377    }
378
379    /// A copy of this call carrying `body`.
380    ///
381    /// Used by [`Either`](crate::Either), which must be able to hand the same
382    /// request to a second extractor after the first declines it.
383    pub(crate) fn clone_with_body(&self, body: Bytes) -> Call {
384        let mut c = Call::new(
385            self.method.clone(),
386            self.uri.clone(),
387            self.headers.clone(),
388            body,
389        );
390        c.params = self.params.clone();
391        c.state = self.state.clone();
392        c.extensions = self.extensions.clone();
393        c
394    }
395
396    /// A lightweight copy of the request for an error renderer.
397    ///
398    /// [`on_error`](crate::AppBuilder::on_error) needs to inspect the request,
399    /// but running the rest of the pipeline consumes the call. This keeps the
400    /// parts an error page can reasonably ask about — method, URI and headers —
401    /// and deliberately not the body, which has usually been consumed by then
402    /// and would be misleading to hand back.
403    pub(crate) fn snapshot_for_error(&self) -> Call {
404        Call::new(
405            self.method.clone(),
406            self.uri.clone(),
407            self.headers.clone(),
408            Bytes::new(),
409        )
410    }
411
412    /// Merge externally-built extensions into this call (used by the engine to
413    /// inject per-connection data such as a pending WebSocket upgrade). Existing
414    /// entries of the same type are overwritten.
415    pub(crate) fn seed_extensions(&mut self, ext: http::Extensions) {
416        self.extensions.extend(ext);
417    }
418
419    /// A shared handle to application state of type `T`, or `None` if no value
420    /// of that type was registered with
421    /// [`AppBuilder::state`](crate::AppBuilder::state). For handler arguments,
422    /// the [`State`](crate::State) extractor is usually more convenient.
423    ///
424    /// ```
425    /// use churust_core::{Churust, Call, TestClient};
426    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
427    /// #[derive(Clone)]
428    /// struct AppName(&'static str);
429    ///
430    /// let app = Churust::server()
431    ///     .state(AppName("churust"))
432    ///     .routing(|r| {
433    ///         r.get("/", |c: Call| async move {
434    ///             format!("app={}", c.state::<AppName>().unwrap().0)
435    ///         });
436    ///     })
437    ///     .build();
438    /// let res = TestClient::new(app).get("/").send().await;
439    /// assert_eq!(res.text(), "app=churust");
440    /// # });
441    /// ```
442    pub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
443        self.state.get::<T>()
444    }
445
446    /// Iterate over the captured path parameters as `(name, value)` pairs, in
447    /// capture order.
448    /// Pairs come back in the order the route captured them.
449    pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)> {
450        self.params.iter()
451    }
452
453    /// The captured path parameters, in capture order.
454    pub fn params(&self) -> &Params {
455        &self.params
456    }
457
458    /// The raw, unparsed value of path parameter `name`, or `None` if the route
459    /// has no such parameter. Use [`param`](Call::param) to parse it into a
460    /// typed value.
461    pub fn param_raw(&self, name: &str) -> Option<&str> {
462        self.params.get(name)
463    }
464
465    /// Parse path parameter `name` into `T`.
466    ///
467    /// Returns a `400 Bad Request` [`Error`] if the parameter is
468    /// missing or fails to parse.
469    ///
470    /// ```
471    /// use churust_core::{Churust, Call, TestClient};
472    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
473    /// let app = Churust::server()
474    ///     .routing(|r| {
475    ///         r.get("/users/{id}", |c: Call| async move {
476    ///             let id: u64 = c.param("id")?;
477    ///             Ok::<_, churust_core::Error>(format!("user {id}"))
478    ///         });
479    ///     })
480    ///     .build();
481    /// let res = TestClient::new(app).get("/users/42").send().await;
482    /// assert_eq!(res.text(), "user 42");
483    /// # });
484    /// ```
485    pub fn param<T>(&self, name: &str) -> Result<T>
486    where
487        T: std::str::FromStr,
488        T::Err: std::fmt::Display,
489    {
490        let raw = self
491            .param_raw(name)
492            .ok_or_else(|| Error::bad_request(format!("missing path param `{name}`")))?;
493        raw.parse::<T>()
494            .map_err(|e| Error::bad_request(format!("bad path param `{name}`: {e}")))
495    }
496
497    /// Take the request body as raw [`Bytes`], leaving the call's body empty.
498    ///
499    /// This consumes the body: a second call returns an empty buffer.
500    ///
501    /// # Prefer [`try_receive_bytes`](Call::try_receive_bytes)
502    ///
503    /// **An empty return does not mean an empty body.** The body now arrives as
504    /// a stream, and this method has no error channel, so a read that fails —
505    /// most importantly one that exceeds `max_body_bytes` — is reported as zero
506    /// bytes. A handler built on this answers `200` with whatever an empty body
507    /// produces, where the caller should have seen `413 Payload Too Large`.
508    ///
509    /// Use [`try_receive_bytes`](Call::try_receive_bytes) and let `?` turn the
510    /// failure into the right status. This method is kept for the case where
511    /// the distinction genuinely does not matter.
512    ///
513    /// ```
514    /// use churust_core::Call;
515    /// use http::{HeaderMap, Method};
516    /// use bytes::Bytes;
517    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
518    /// let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("data"));
519    /// assert_eq!(&call.receive_bytes().await[..], b"data");
520    /// assert!(call.receive_bytes().await.is_empty()); // already consumed
521    /// # });
522    /// ```
523    pub async fn receive_bytes(&mut self) -> Bytes {
524        self.try_receive_bytes().await.unwrap_or_default()
525    }
526
527    /// Read the whole body, surfacing the size limit as an error.
528    ///
529    /// [`receive_bytes`](Call::receive_bytes) exists for callers that cannot
530    /// report failure and yields an empty buffer instead; extractors should
531    /// prefer this so an oversized body becomes `413` rather than a confusing
532    /// deserialization error.
533    pub async fn try_receive_bytes(&mut self) -> Result<Bytes> {
534        // The per-route cap, when one is set, is the ceiling this collection
535        // must respect — see `try_receive_bytes_within`.
536        let cap = self
537            .get::<crate::extract::RouteBodyLimit>()
538            .map(|crate::extract::RouteBodyLimit(n)| n)
539            .unwrap_or(usize::MAX);
540        self.try_receive_bytes_within(cap).await
541    }
542
543    /// Collect the body, refusing it the moment it exceeds `max`.
544    ///
545    /// The distinction from checking afterwards is memory, not status. A body
546    /// gathered first and measured second has already been allocated: a 16 MiB
547    /// upload against a 64 KiB route cap peaked above 32 MiB — `BytesMut`
548    /// doubles — before anything refused it, and N concurrent requests
549    /// multiplied that. The `413` was correct and arrived far too late to be
550    /// the protection it looked like.
551    ///
552    /// Errors with `413 Payload Too Large` as soon as the accumulated length
553    /// crosses `max`, so the peak is bounded by the cap rather than by what the
554    /// client chose to send.
555    pub async fn try_receive_bytes_within(&mut self, max: usize) -> Result<Bytes> {
556        match std::mem::replace(&mut self.body, RequestBody::Buffered(Bytes::new())) {
557            RequestBody::Buffered(b) => {
558                if b.len() > max {
559                    return Err(Error::new(
560                        StatusCode::PAYLOAD_TOO_LARGE,
561                        "request body too large",
562                    ));
563                }
564                Ok(b)
565            }
566            RequestBody::Stream(cell) => {
567                let Some(mut s) = cell.lock().ok().and_then(|mut g| g.take()) else {
568                    return Ok(Bytes::new());
569                };
570                use futures_util::StreamExt;
571                let mut buf = bytes::BytesMut::new();
572                while let Some(chunk) = s.next().await {
573                    let chunk = chunk?;
574                    // Check before extending, so the allocation never happens.
575                    if buf.len().saturating_add(chunk.len()) > max {
576                        return Err(Error::new(
577                            StatusCode::PAYLOAD_TOO_LARGE,
578                            "request body too large",
579                        ));
580                    }
581                    buf.extend_from_slice(&chunk);
582                }
583                Ok(buf.freeze())
584            }
585        }
586    }
587
588    /// Take the body as a stream, without buffering it.
589    ///
590    /// This is how a large upload is processed without holding it in memory.
591    /// Returns `None` if the body has already been consumed. A body that
592    /// arrived buffered — from [`Call::new`] or a test client — yields a
593    /// single-chunk stream, so a handler need not care which it got.
594    ///
595    /// The server-wide `max_body_bytes` still applies: exceeding it surfaces as
596    /// an error item in the stream rather than a `413`, because the response
597    /// has usually begun by then.
598    pub fn body_stream(&mut self) -> Option<BodyStream> {
599        match std::mem::replace(&mut self.body, RequestBody::Buffered(Bytes::new())) {
600            RequestBody::Stream(cell) => cell.lock().ok().and_then(|mut g| g.take()),
601            RequestBody::Buffered(b) if !b.is_empty() => {
602                Some(Box::pin(futures_util::stream::once(async move { Ok(b) })))
603            }
604            RequestBody::Buffered(_) => None,
605        }
606    }
607
608    /// Attach a streaming body. Used by the engine.
609    pub(crate) fn with_body_stream(mut self, stream: BodyStream) -> Self {
610        self.body = RequestBody::Stream(std::sync::Mutex::new(Some(stream)));
611        self
612    }
613
614    /// Take the request body decoded as UTF-8, consuming it.
615    ///
616    /// Returns a `400 Bad Request` [`Error`] if the body is not
617    /// valid UTF-8.
618    ///
619    /// ```
620    /// use churust_core::Call;
621    /// use http::{HeaderMap, Method};
622    /// use bytes::Bytes;
623    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
624    /// let mut call = Call::new(Method::POST, "/".parse().unwrap(), HeaderMap::new(), Bytes::from("ping"));
625    /// assert_eq!(call.receive_text().await.unwrap(), "ping");
626    /// # });
627    /// ```
628    pub async fn receive_text(&mut self) -> Result<String> {
629        // `try_receive_bytes`, not `receive_bytes`: the latter swallows a read
630        // error into an empty payload, which would turn an over-limit body into
631        // an empty string rather than a `413`.
632        let bytes = self.try_receive_bytes().await?;
633        crate::extract::check_body_limit(self, bytes.len())?;
634        String::from_utf8(bytes.to_vec())
635            .map_err(|e| Error::bad_request(format!("body is not valid UTF-8: {e}")))
636    }
637
638    /// Insert a per-call typed value into the call's extension map, keyed by its
639    /// type. Typically used by a middleware to attach context (e.g. an
640    /// authenticated principal) that a downstream extractor or handler reads via
641    /// [`get`](Call::get). Inserting a second value of the same type replaces
642    /// the first.
643    ///
644    /// ```
645    /// use churust_core::Call;
646    /// use http::{HeaderMap, Method};
647    /// use bytes::Bytes;
648    ///
649    /// #[derive(Clone, PartialEq, Debug)]
650    /// struct UserId(u32);
651    ///
652    /// let mut call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
653    /// call.insert(UserId(7));
654    /// assert_eq!(call.get::<UserId>(), Some(UserId(7)));
655    /// ```
656    pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
657        self.extensions.insert(value);
658    }
659
660    /// Get a clone of the per-call value of type `T` previously stored with
661    /// [`insert`](Call::insert), or `None` if none was stored.
662    ///
663    /// ```
664    /// use churust_core::Call;
665    /// use http::{HeaderMap, Method};
666    /// use bytes::Bytes;
667    ///
668    /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
669    /// assert_eq!(call.get::<u32>(), None);
670    /// ```
671    pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {
672        self.extensions.get::<T>().cloned()
673    }
674
675    // ---- response convenience (sync; body is in memory) ----
676
677    /// Build a `200 OK` `text/plain` [`Response`] — a shorthand for
678    /// [`Response::text`].
679    ///
680    /// ```
681    /// use churust_core::Call;
682    /// use http::{HeaderMap, Method};
683    /// use bytes::Bytes;
684    ///
685    /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
686    /// let res = call.respond_text("ok");
687    /// assert_eq!(res.body.as_slice(), Some(&b"ok"[..]));
688    /// ```
689    pub fn respond_text(&self, body: impl Into<String>) -> Response {
690        Response::text(body)
691    }
692
693    /// Build an empty-bodied [`Response`] with the given status — a shorthand
694    /// for [`Response::new`].
695    ///
696    /// ```
697    /// use churust_core::Call;
698    /// use http::{HeaderMap, Method, StatusCode};
699    /// use bytes::Bytes;
700    ///
701    /// let call = Call::new(Method::GET, "/".parse().unwrap(), HeaderMap::new(), Bytes::new());
702    /// assert_eq!(call.respond_status(StatusCode::ACCEPTED).status, StatusCode::ACCEPTED);
703    /// ```
704    pub fn respond_status(&self, status: StatusCode) -> Response {
705        Response::new(status)
706    }
707}
708
709fn form_urlencoded_first(query: &str, key: &str) -> Option<String> {
710    for pair in query.split('&') {
711        let mut it = pair.splitn(2, '=');
712        let k = it.next().unwrap_or("");
713        if k == key {
714            let v = it.next().unwrap_or("");
715            return Some(percent_decode(v));
716        }
717    }
718    None
719}
720
721fn percent_decode(s: &str) -> String {
722    // Minimal `+` and %XX decoding sufficient for v1 query parsing.
723    let bytes = s.as_bytes();
724    let mut out = Vec::with_capacity(bytes.len());
725    let mut i = 0;
726    while i < bytes.len() {
727        match bytes[i] {
728            b'+' => {
729                out.push(b' ');
730                i += 1;
731            }
732            b'%' if i + 2 < bytes.len() => {
733                let hi = (bytes[i + 1] as char).to_digit(16);
734                let lo = (bytes[i + 2] as char).to_digit(16);
735                match (hi, lo) {
736                    (Some(h), Some(l)) => {
737                        out.push((h * 16 + l) as u8);
738                        i += 3;
739                    }
740                    _ => {
741                        out.push(bytes[i]);
742                        i += 1;
743                    }
744                }
745            }
746            b => {
747                out.push(b);
748                i += 1;
749            }
750        }
751    }
752    String::from_utf8_lossy(&out).into_owned()
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758
759    fn call(path: &str, body: &str) -> Call {
760        Call::new(
761            Method::GET,
762            path.parse::<Uri>().unwrap(),
763            HeaderMap::new(),
764            Bytes::from(body.to_string()),
765        )
766    }
767
768    #[test]
769    fn reads_method_and_path() {
770        let c = call("/a/b?x=1", "");
771        assert_eq!(c.method(), &Method::GET);
772        assert_eq!(c.path(), "/a/b");
773    }
774
775    #[test]
776    fn parses_query() {
777        let c = call("/s?q=hello+world&n=5", "");
778        assert_eq!(c.query("q").as_deref(), Some("hello world"));
779        assert_eq!(c.query("n").as_deref(), Some("5"));
780        assert_eq!(c.query("missing"), None);
781    }
782
783    #[test]
784    fn parses_query_percent_encoded() {
785        let c = call("/s?q=hello%20world", "");
786        assert_eq!(c.query("q").as_deref(), Some("hello world"));
787    }
788
789    #[test]
790    fn parses_path_param() {
791        let mut c = call("/users/42", "");
792        let mut p = crate::call::Params::new();
793        p.insert("id".to_string(), "42".to_string());
794        c.set_params(p);
795        let id: u64 = c.param("id").unwrap();
796        assert_eq!(id, 42);
797        assert!(c.param::<u64>("missing").is_err());
798    }
799
800    #[tokio::test]
801    async fn receives_text_body() {
802        let mut c = call("/", "payload");
803        assert_eq!(c.receive_text().await.unwrap(), "payload");
804    }
805
806    #[test]
807    fn state_round_trips() {
808        use crate::state::StateMap;
809        let mut c = call("/", "");
810        let mut sm = StateMap::default();
811        sm.insert(99u32);
812        c.set_state(std::sync::Arc::new(sm));
813        assert_eq!(*c.state::<u32>().unwrap(), 99);
814    }
815
816    #[test]
817    fn the_uri_authority_outranks_a_disagreeing_host_header() {
818        // Both spellings of the host can arrive at once: over HTTP/2 a peer may
819        // append an ordinary `host` field beside `:authority` (h2 forwards it
820        // untouched, and hyper builds the URI authority from the pseudo-header
821        // alone), and over HTTP/1.1 an absolute-form target carries an
822        // authority beside the mandatory `Host`. Both RFCs say the authority
823        // is the request target and the header is to be ignored, so the guard
824        // and anything else asking "which site is this for" must agree with
825        // whatever an intermediary routed on.
826        let mut headers = HeaderMap::new();
827        headers.insert(
828            http::header::HOST,
829            http::HeaderValue::from_static("admin.example.com"),
830        );
831        let c = Call::new(
832            Method::GET,
833            "https://www.example.com/".parse::<Uri>().unwrap(),
834            headers,
835            Bytes::new(),
836        );
837        assert_eq!(c.host().as_deref(), Some("www.example.com"));
838    }
839
840    #[test]
841    fn the_host_header_is_still_read_when_the_uri_has_no_authority() {
842        // The origin-form HTTP/1.1 request, which is the overwhelmingly common
843        // shape: the header is the only host signal there is.
844        let mut headers = HeaderMap::new();
845        headers.insert(
846            http::header::HOST,
847            http::HeaderValue::from_static("admin.example.com:8443"),
848        );
849        let c = Call::new(
850            Method::GET,
851            "/".parse::<Uri>().unwrap(),
852            headers,
853            Bytes::new(),
854        );
855        assert_eq!(c.host().as_deref(), Some("admin.example.com"));
856    }
857
858    #[test]
859    fn extensions_round_trip() {
860        #[derive(Clone, PartialEq, Debug)]
861        struct User(u32);
862        let mut c = call("/", "");
863        assert!(c.get::<User>().is_none());
864        c.insert(User(7));
865        assert_eq!(c.get::<User>(), Some(User(7)));
866    }
867}