Skip to main content

alux_http_actix/
input.rs

1//! Reads each handler argument with the extractor actix-web states for its role.
2
3use actix_web::dev::Payload;
4use actix_web::error::ErrorBadRequest;
5use actix_web::http::header;
6use actix_web::web::{Bytes, Form, Json, Path, Query};
7use actix_web::{Error, FromRequest, HttpRequest};
8use alux_http::{FromPartsAlg, read_cookies, read_header_name};
9use alux_http_parts::ReadParts;
10use core::fmt::Display;
11use core::future::Future;
12use core::marker::PhantomData;
13use serde::de::DeserializeOwned;
14
15/// Marks a value extracted from the request path by actix-web.
16pub struct ActixPathInput<Input>(PhantomData<Input>);
17/// Marks a value extracted from the query string by actix-web.
18pub struct ActixQueryInput<Input>(PhantomData<Input>);
19/// Marks a JSON value extracted from the request body by actix-web.
20pub struct ActixBodyInput<Input>(PhantomData<Input>);
21/// Marks a form-encoded value extracted from the request body by actix-web.
22pub struct ActixFormInput<Input>(PhantomData<Input>);
23/// Marks a value extracted through actix-web's own request extractor.
24pub struct ActixRequestInput<Input>(PhantomData<Input>);
25
26/// Reads one handler argument from a request actix-web is answering.
27pub(crate) trait ActixInputAlg<Output> {
28    fn extract(request: &HttpRequest, payload: &mut Payload) -> impl Future<Output = Result<Output, Error>>;
29}
30
31macro_rules! actix_input {
32    ($marker:ident, $extractor:ident) => {
33        impl<Input> ActixInputAlg<Input> for $marker<Input>
34        where
35            Input: DeserializeOwned + 'static,
36        {
37            async fn extract(request: &HttpRequest, payload: &mut Payload) -> Result<Input, Error> {
38                Ok($extractor::<Input>::from_request(request, payload).await?.into_inner())
39            }
40        }
41    };
42}
43
44actix_input!(ActixPathInput, Path);
45actix_input!(ActixQueryInput, Query);
46actix_input!(ActixBodyInput, Json);
47actix_input!(ActixFormInput, Form);
48
49impl<Input> ActixInputAlg<Input> for ActixRequestInput<Input>
50where
51    Input: FromRequest + 'static,
52{
53    async fn extract(request: &HttpRequest, payload: &mut Payload) -> Result<Input, Error> {
54        Input::from_request(request, payload).await.map_err(Into::into)
55    }
56}
57
58/// Marks a value read from the cookies a caller sent.
59pub struct ActixCookieInput<Input>(PhantomData<Input>);
60
61/// Marks a value read from the headers a caller sent.
62pub struct ActixHeaderInput<Input>(PhantomData<Input>);
63
64/// Reads the headers a caller sent into the argument an author asked for.
65///
66/// Headers are names and values, so what reads them is what reads any other name-and-value product.
67/// A framework's own extractor is what the endpoint-context role states instead.
68fn headers_of<Input>(stated: impl Iterator<Item = (String, String)>) -> Result<Input, String>
69where
70    Input: DeserializeOwned,
71{
72    let named = stated.map(|(name, value)| (read_header_name(&name), value)).collect::<Vec<_>>();
73    let encoded = serde_urlencoded::to_string(&named).map_err(|error| error.to_string())?;
74
75    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
76}
77
78/// Reads the cookies a header states into the argument an author asked for.
79///
80/// Cookies are names and values, so what reads them is what reads any other name-and-value product.
81fn cookies_of<Input>(header: Option<&str>) -> Result<Input, String>
82where
83    Input: DeserializeOwned,
84{
85    let stated = read_cookies(header.unwrap_or_default());
86    let encoded = serde_urlencoded::to_string(&stated).map_err(|error| error.to_string())?;
87
88    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
89}
90
91impl<Input> ActixInputAlg<Input> for ActixCookieInput<Input>
92where
93    Input: DeserializeOwned + 'static,
94{
95    async fn extract(request: &HttpRequest, _payload: &mut Payload) -> Result<Input, Error> {
96        let header = request.headers().get(header::COOKIE).and_then(|value| value.to_str().ok());
97
98        cookies_of(header).map_err(ErrorBadRequest)
99    }
100}
101
102impl<Input> ActixInputAlg<Input> for ActixHeaderInput<Input>
103where
104    Input: DeserializeOwned + 'static,
105{
106    async fn extract(request: &HttpRequest, _payload: &mut Payload) -> Result<Input, Error> {
107        let stated = request
108            .headers()
109            .iter()
110            .filter_map(|(name, value)| Some((name.to_string(), value.to_str().ok()?.to_owned())));
111
112        headers_of(stated).map_err(ErrorBadRequest)
113    }
114}
115
116/// Marks an argument read from a body arriving as parts.
117pub struct ActixMultipartInput<Input>(PhantomData<Input>);
118
119impl<Input> ActixInputAlg<Input> for ActixMultipartInput<Input>
120where
121    Input: FromPartsAlg<ReadParts> + 'static,
122    Input::Error: Display,
123{
124    async fn extract(request: &HttpRequest, payload: &mut Payload) -> Result<Input, Error> {
125        let media_type = request.headers().get(header::CONTENT_TYPE).and_then(|value| value.to_str().ok());
126        let media_type = media_type.ok_or_else(|| ErrorBadRequest("the body states no media type"))?.to_owned();
127        let body = Bytes::from_request(request, payload).await?;
128        let parts = ReadParts::new(&media_type, body.to_vec()).map_err(ErrorBadRequest)?;
129
130        Input::from_parts(parts).await.map_err(|error| ErrorBadRequest(error.to_string()))
131    }
132}
133
134/// Reads the whole argument product one endpoint states, in declaration order.
135pub(crate) trait ActixInputsAlg<Outputs> {
136    fn extract(request: &HttpRequest, payload: &mut Payload) -> impl Future<Output = Result<Outputs, Error>>;
137}
138
139impl ActixInputsAlg<()> for () {
140    async fn extract(_request: &HttpRequest, _payload: &mut Payload) -> Result<(), Error> {
141        Ok(())
142    }
143}
144
145macro_rules! actix_inputs {
146    ($($input:ident => $output:ident),+ $(,)?) => {
147        impl<$($input, $output),+> ActixInputsAlg<($($output,)+)> for ($($input,)+)
148        where
149            $($input: ActixInputAlg<$output>,)+
150        {
151            async fn extract(request: &HttpRequest, payload: &mut Payload) -> Result<($($output,)+), Error> {
152                Ok(($($input::extract(request, payload).await?,)+))
153            }
154        }
155    };
156}
157
158actix_inputs!(I1 => O1);
159actix_inputs!(I1 => O1, I2 => O2);
160actix_inputs!(I1 => O1, I2 => O2, I3 => O3);
161actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4);
162actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5);
163actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6);
164actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7);
165actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8);
166actix_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9);
167actix_inputs!(
168    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10
169);
170actix_inputs!(
171    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
172    I11 => O11
173);
174actix_inputs!(
175    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
176    I11 => O11, I12 => O12
177);
178actix_inputs!(
179    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
180    I11 => O11, I12 => O12, I13 => O13
181);
182actix_inputs!(
183    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
184    I11 => O11, I12 => O12, I13 => O13, I14 => O14
185);
186actix_inputs!(
187    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
188    I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15
189);
190actix_inputs!(
191    I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10,
192    I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15, I16 => O16
193);