Skip to main content

alux_http_warp/
input.rs

1//! Reads each handler argument from what warp's filters gathered.
2
3use crate::WarpRequest;
4use alux_http::{FromPartsAlg, read_cookies, read_header_name};
5use alux_http::{HttpErrorAlg, HttpStatus};
6use alux_http_parts::ReadParts;
7use core::error::Error;
8use core::fmt::{self, Display};
9use core::future::Future;
10use core::marker::PhantomData;
11use core::str::FromStr;
12use serde::de::DeserializeOwned;
13use warp::http::HeaderMap;
14use warp::http::header;
15
16/// Marks a value read from the segments a path bound.
17pub struct WarpPathInput<Input>(PhantomData<Input>);
18/// Marks a value read from the query string.
19pub struct WarpQueryInput<Input>(PhantomData<Input>);
20/// Marks a JSON value read from the request body.
21pub struct WarpBodyInput<Input>(PhantomData<Input>);
22/// Marks a form-encoded value read from the request body.
23pub struct WarpFormInput<Input>(PhantomData<Input>);
24/// Marks the request body taken as it arrived.
25pub struct WarpRawBodyInput<Input>(PhantomData<Input>);
26/// Marks a value read from the headers the caller sent.
27pub struct WarpHeadInput<Input>(PhantomData<Input>);
28
29/// States that an argument could not be read from where its role says it comes from.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct WarpError {
32    message: String,
33}
34
35impl WarpError {
36    fn unreadable(role: &str, reason: &str) -> Self {
37        Self { message: format!("the {role} could not be read: {reason}") }
38    }
39}
40
41impl HttpErrorAlg for WarpError {
42    const HTTP_STATUSES: &'static [HttpStatus] = &[HttpStatus::BAD_REQUEST];
43
44    fn http_status(&self) -> HttpStatus {
45        HttpStatus::BAD_REQUEST
46    }
47
48    fn http_message(&self) -> String {
49        self.message.clone()
50    }
51}
52
53impl Display for WarpError {
54    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55        formatter.write_str(&self.message)
56    }
57}
58
59impl Error for WarpError {}
60
61/// Reads the handler argument a path's captured segments state.
62pub trait FromCapturedAlg: Sized {
63    /// Reads the argument from the segments the path bound, in declaration order.
64    fn from_captured(captures: &[String]) -> Option<Self>;
65}
66
67macro_rules! captured {
68    ($($input:ty),+ $(,)?) => {
69        $(
70            impl FromCapturedAlg for $input {
71                fn from_captured(captures: &[String]) -> Option<Self> {
72                    captures.first()?.parse().ok()
73                }
74            }
75        )+
76    };
77}
78
79captured!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool, char, String);
80
81macro_rules! captured_product {
82    ($($input:ident => $index:tt),+ $(,)?) => {
83        impl<$($input),+> FromCapturedAlg for ($($input,)+)
84        where
85            $($input: FromStr,)+
86        {
87            fn from_captured(captures: &[String]) -> Option<Self> {
88                Some(($(captures.get($index)?.parse::<$input>().ok()?,)+))
89            }
90        }
91    };
92}
93
94captured_product!(A => 0, B => 1);
95captured_product!(A => 0, B => 1, C => 2);
96captured_product!(A => 0, B => 1, C => 2, D => 3);
97captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4);
98captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5);
99captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6);
100captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7);
101captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8);
102captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9);
103captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10);
104captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10, L => 11);
105captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10, L => 11, M => 12);
106captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10, L => 11, M => 12, N => 13);
107captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10, L => 11, M => 12, N => 13, O => 14);
108captured_product!(A => 0, B => 1, C => 2, D => 3, E => 4, F => 5, G => 6, H => 7, I => 8, J => 9, K => 10, L => 11, M => 12, N => 13, O => 14, P => 15);
109
110/// Reads the handler argument a request body states, taken as it arrived.
111pub trait FromRawAlg: Sized {
112    /// Reads the argument from the bytes the caller sent.
113    fn from_raw(body: &[u8]) -> Option<Self>;
114}
115
116impl FromRawAlg for Vec<u8> {
117    fn from_raw(body: &[u8]) -> Option<Self> {
118        Some(body.to_vec())
119    }
120}
121
122impl FromRawAlg for String {
123    fn from_raw(body: &[u8]) -> Option<Self> {
124        Self::from_utf8(body.to_vec()).ok()
125    }
126}
127
128/// Reads the handler argument the headers state.
129pub trait FromHeadersAlg: Sized {
130    /// Reads the argument from the headers the caller sent.
131    fn from_headers(headers: &HeaderMap) -> Option<Self>;
132}
133
134impl FromHeadersAlg for HeaderMap {
135    fn from_headers(headers: &HeaderMap) -> Option<Self> {
136        Some(headers.clone())
137    }
138}
139
140/// Reads one handler argument out of what warp's filters gathered.
141pub(crate) trait WarpInputAlg<Output> {
142    fn extract(request: &WarpRequest) -> impl Future<Output = Result<Output, WarpError>> + Send;
143}
144
145impl<Input> WarpInputAlg<Input> for WarpPathInput<Input>
146where
147    Input: FromCapturedAlg,
148{
149    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
150        Input::from_captured(request.captures())
151            .ok_or_else(|| WarpError::unreadable("path", "it states something else"))
152    }
153}
154
155impl<Input> WarpInputAlg<Input> for WarpQueryInput<Input>
156where
157    Input: DeserializeOwned,
158{
159    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
160        serde_urlencoded::from_str(request.query()).map_err(|error| WarpError::unreadable("query", &error.to_string()))
161    }
162}
163
164impl<Input> WarpInputAlg<Input> for WarpBodyInput<Input>
165where
166    Input: DeserializeOwned,
167{
168    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
169        serde_json::from_slice(request.body()).map_err(|error| WarpError::unreadable("body", &error.to_string()))
170    }
171}
172
173impl<Input> WarpInputAlg<Input> for WarpFormInput<Input>
174where
175    Input: DeserializeOwned,
176{
177    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
178        serde_urlencoded::from_bytes(request.body()).map_err(|error| WarpError::unreadable("form", &error.to_string()))
179    }
180}
181
182impl<Input> WarpInputAlg<Input> for WarpRawBodyInput<Input>
183where
184    Input: FromRawAlg,
185{
186    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
187        Input::from_raw(request.body()).ok_or_else(|| WarpError::unreadable("body", "it states something else"))
188    }
189}
190
191impl<Input> WarpInputAlg<Input> for WarpHeadInput<Input>
192where
193    Input: FromHeadersAlg,
194{
195    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
196        Input::from_headers(request.headers())
197            .ok_or_else(|| WarpError::unreadable("headers", "they state something else"))
198    }
199}
200
201/// Marks a value read from the cookies a caller sent.
202pub struct WarpCookieInput<Input>(PhantomData<Input>);
203
204/// Marks a value read from the headers a caller sent.
205pub struct WarpHeaderInput<Input>(PhantomData<Input>);
206
207/// Reads the headers a caller sent into the argument an author asked for.
208///
209/// Headers are names and values, so what reads them is what reads any other name-and-value product.
210/// A framework's own extractor is what the endpoint-context role states instead.
211fn headers_of<Input>(stated: impl Iterator<Item = (String, String)>) -> Result<Input, String>
212where
213    Input: DeserializeOwned,
214{
215    let named = stated.map(|(name, value)| (read_header_name(&name), value)).collect::<Vec<_>>();
216    let encoded = serde_urlencoded::to_string(&named).map_err(|error| error.to_string())?;
217
218    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
219}
220
221/// Reads the cookies a header states into the argument an author asked for.
222///
223/// Cookies are names and values, so what reads them is what reads any other name-and-value product.
224fn cookies_of<Input>(header: Option<&str>) -> Result<Input, String>
225where
226    Input: DeserializeOwned,
227{
228    let stated = read_cookies(header.unwrap_or_default());
229    let encoded = serde_urlencoded::to_string(&stated).map_err(|error| error.to_string())?;
230
231    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
232}
233
234impl<Input> WarpInputAlg<Input> for WarpCookieInput<Input>
235where
236    Input: DeserializeOwned,
237{
238    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
239        let header = request.headers().get(header::COOKIE).and_then(|value| value.to_str().ok());
240
241        cookies_of(header).map_err(|error| WarpError::unreadable("cookies", &error))
242    }
243}
244
245impl<Input> WarpInputAlg<Input> for WarpHeaderInput<Input>
246where
247    Input: DeserializeOwned,
248{
249    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
250        let stated = request
251            .headers()
252            .iter()
253            .filter_map(|(name, value)| Some((name.to_string(), value.to_str().ok()?.to_owned())));
254
255        headers_of(stated).map_err(|error| WarpError::unreadable("headers", &error))
256    }
257}
258
259/// Marks an argument read from a body arriving as parts.
260pub struct WarpMultipartInput<Input>(PhantomData<Input>);
261
262impl<Input> WarpInputAlg<Input> for WarpMultipartInput<Input>
263where
264    Input: FromPartsAlg<ReadParts> + Send,
265    Input::Error: Display,
266{
267    async fn extract(request: &WarpRequest) -> Result<Input, WarpError> {
268        let media_type = request
269            .headers()
270            .get(header::CONTENT_TYPE)
271            .and_then(|value| value.to_str().ok())
272            .ok_or_else(|| WarpError::unreadable("parts", "the body states no media type"))?;
273        let parts = ReadParts::new(media_type, request.body().to_vec())
274            .map_err(|error| WarpError::unreadable("parts", &error.to_string()))?;
275
276        Input::from_parts(parts).await.map_err(|error| WarpError::unreadable("parts", &error.to_string()))
277    }
278}
279
280/// Reads the whole argument product one endpoint states, in declaration order.
281pub(crate) trait WarpInputsAlg<Outputs> {
282    fn extract(request: &WarpRequest) -> impl Future<Output = Result<Outputs, WarpError>> + Send;
283}
284
285impl WarpInputsAlg<()> for () {
286    async fn extract(_request: &WarpRequest) -> Result<(), WarpError> {
287        Ok(())
288    }
289}
290
291macro_rules! warp_inputs {
292    ($($input:ident => $output:ident),+ $(,)?) => {
293        impl<$($input, $output),+> WarpInputsAlg<($($output,)+)> for ($($input,)+)
294        where
295            $($input: WarpInputAlg<$output>, $output: Send,)+
296        {
297            async fn extract(request: &WarpRequest) -> Result<($($output,)+), WarpError> {
298                Ok(($($input::extract(request).await?,)+))
299            }
300        }
301    };
302}
303
304warp_inputs!(I1 => O1);
305warp_inputs!(I1 => O1, I2 => O2);
306warp_inputs!(I1 => O1, I2 => O2, I3 => O3);
307warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4);
308warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5);
309warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6);
310warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7);
311warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8);
312warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9);
313warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10);
314warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11);
315warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12);
316warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13);
317warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14);
318warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15);
319warp_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15, I16 => O16);