Skip to main content

alux_http_rocket/
input.rs

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