Skip to main content

multistore_cf_workers/
request.rs

1//! Request parsing helpers for Cloudflare Workers.
2//!
3//! Provides [`RequestParts`] to extract owned HTTP metadata from a
4//! `web_sys::Request`, and convert it into the borrowed
5//! [`RequestInfo`](multistore::route_handler::RequestInfo) required by the gateway.
6
7use crate::body::JsBody;
8use crate::response::headermap_from_js;
9use http::{HeaderMap, Method, Uri};
10use multistore::route_handler::RequestInfo;
11
12/// Owned HTTP request metadata extracted from a `web_sys::Request`.
13///
14/// Workers passes a `web_sys::Request` with borrowed JS strings and a
15/// `ReadableStream` body.  The gateway expects a [`RequestInfo`] that
16/// borrows from Rust-owned data, so this struct bridges the gap by
17/// owning the parsed method, path, query, and headers.
18///
19/// # Example
20///
21/// ```rust,ignore
22/// let (parts, body) = RequestParts::from_web_sys(&req)?;
23/// let result = gateway
24///     .handle_request(&parts.as_request_info(), body, collect_js_body)
25///     .await;
26/// ```
27pub struct RequestParts {
28    /// The HTTP method.
29    pub method: Method,
30    /// The URL path (e.g. `"/bucket/key"`).
31    pub path: String,
32    /// The raw query string, if present.
33    pub query: Option<String>,
34    /// The HTTP request headers.
35    pub headers: HeaderMap,
36}
37
38impl RequestParts {
39    /// Parse a `web_sys::Request` into owned request metadata and a
40    /// zero-copy [`JsBody`].
41    ///
42    /// Extracts the body stream **before** reading headers, so the
43    /// `ReadableStream` is never locked.
44    pub fn from_web_sys(req: &web_sys::Request) -> Result<(Self, JsBody), String> {
45        let body = JsBody::new(req.body());
46
47        let method: Method = req
48            .method()
49            .parse()
50            .map_err(|e| format!("invalid method: {e}"))?;
51
52        let uri: Uri = req.url().parse().map_err(|e| format!("invalid URL: {e}"))?;
53
54        let path = percent_encoding::percent_decode_str(uri.path())
55            .decode_utf8_lossy()
56            .to_string();
57        let query = uri.query().map(|q| q.to_string());
58        let headers = headermap_from_js(&req.headers());
59
60        Ok((
61            Self {
62                method,
63                path,
64                query,
65                headers,
66            },
67            body,
68        ))
69    }
70
71    /// Borrow this struct as a [`RequestInfo`] for gateway dispatch.
72    pub fn as_request_info(&self) -> RequestInfo<'_> {
73        RequestInfo::new(
74            &self.method,
75            &self.path,
76            self.query.as_deref(),
77            &self.headers,
78            None,
79        )
80    }
81}