Skip to main content

alux_http_axum/
input.rs

1use crate::AxumParts;
2use alux_http::{FromPartsAlg, read_cookies, read_header_name};
3use axum::body::Body;
4use axum::extract::{FromRequest, FromRequestParts, Multipart, Path, Query, Request};
5use axum::http::request::Parts;
6use axum::http::{StatusCode, header};
7use axum::response::{IntoResponse, Response};
8use axum::{Form, Json};
9use core::fmt::Display;
10use core::future::Future;
11use core::marker::PhantomData;
12use serde::de::DeserializeOwned;
13
14/// Marks a value extracted from the request path by axum.
15pub struct AxumPathInput<Input>(PhantomData<Input>);
16/// Marks a value extracted from the query string by axum.
17pub struct AxumQueryInput<Input>(PhantomData<Input>);
18/// Marks a JSON value extracted from the request body by axum.
19pub struct AxumBodyInput<Input>(PhantomData<Input>);
20/// Marks a form-encoded value extracted from the request body by axum.
21pub struct AxumFormInput<Input>(PhantomData<Input>);
22/// Marks the request body taken by axum as it arrived.
23pub struct AxumRawBodyInput<Input>(PhantomData<Input>);
24/// Marks a value extracted from the request head by axum.
25pub struct AxumPartsInput<Input>(PhantomData<Input>);
26
27/// Reads one handler argument from a request axum has taken apart.
28///
29/// The head is read in place and the body is taken once, so an endpoint reading the body reads it
30/// exactly as a framework handler would.
31pub(crate) trait AxumInputAlg<Output> {
32    fn extract(parts: &mut Parts, body: &mut Option<Body>) -> impl Future<Output = Result<Output, Response>> + Send;
33}
34
35impl<Input> AxumInputAlg<Input> for AxumPartsInput<Input>
36where
37    Input: FromRequestParts<()> + Send,
38{
39    async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
40        Input::from_request_parts(parts, &()).await.map_err(IntoResponse::into_response)
41    }
42}
43
44macro_rules! axum_parts_input {
45    ($marker:ident, $extractor:ident) => {
46        impl<Input> AxumInputAlg<Input> for $marker<Input>
47        where
48            Input: DeserializeOwned + Send,
49        {
50            async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
51                Ok($extractor::<Input>::from_request_parts(parts, &()).await.map_err(IntoResponse::into_response)?.0)
52            }
53        }
54    };
55}
56
57axum_parts_input!(AxumPathInput, Path);
58axum_parts_input!(AxumQueryInput, Query);
59
60/// Rebuilds the request one body extractor reads, taking the body it consumes.
61fn taken(parts: &Parts, body: &mut Option<Body>) -> Request {
62    Request::from_parts(parts.clone(), body.take().unwrap_or_default())
63}
64
65macro_rules! axum_body_input {
66    ($marker:ident, $extractor:ident) => {
67        impl<Input> AxumInputAlg<Input> for $marker<Input>
68        where
69            Input: DeserializeOwned + Send,
70        {
71            async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
72                let request = taken(parts, body);
73
74                Ok($extractor::<Input>::from_request(request, &()).await.map_err(IntoResponse::into_response)?.0)
75            }
76        }
77    };
78}
79
80axum_body_input!(AxumBodyInput, Json);
81axum_body_input!(AxumFormInput, Form);
82
83impl<Input> AxumInputAlg<Input> for AxumRawBodyInput<Input>
84where
85    Input: FromRequest<()> + Send,
86{
87    async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
88        let request = taken(parts, body);
89
90        Input::from_request(request, &()).await.map_err(IntoResponse::into_response)
91    }
92}
93
94/// Marks a value read from the cookies a caller sent.
95pub struct AxumCookieInput<Input>(PhantomData<Input>);
96
97/// Marks a value read from the headers a caller sent.
98pub struct AxumHeaderInput<Input>(PhantomData<Input>);
99
100/// Reads the headers a caller sent into the argument an author asked for.
101///
102/// Headers are names and values, so what reads them is what reads any other name-and-value product.
103/// A framework's own extractor is what the endpoint-context role states instead.
104fn headers_of<Input>(stated: impl Iterator<Item = (String, String)>) -> Result<Input, String>
105where
106    Input: DeserializeOwned,
107{
108    let named = stated.map(|(name, value)| (read_header_name(&name), value)).collect::<Vec<_>>();
109    let encoded = serde_urlencoded::to_string(&named).map_err(|error| error.to_string())?;
110
111    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
112}
113
114/// Reads the cookies a header states into the argument an author asked for.
115///
116/// Cookies are names and values, so what reads them is what reads any other name-and-value product.
117fn cookies_of<Input>(header: Option<&str>) -> Result<Input, String>
118where
119    Input: DeserializeOwned,
120{
121    let stated = read_cookies(header.unwrap_or_default());
122    let encoded = serde_urlencoded::to_string(&stated).map_err(|error| error.to_string())?;
123
124    serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
125}
126
127impl<Input> AxumInputAlg<Input> for AxumCookieInput<Input>
128where
129    Input: DeserializeOwned + Send,
130{
131    async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
132        let header = parts.headers.get(header::COOKIE).and_then(|value| value.to_str().ok());
133
134        cookies_of(header).map_err(|error| (StatusCode::BAD_REQUEST, error).into_response())
135    }
136}
137
138impl<Input> AxumInputAlg<Input> for AxumHeaderInput<Input>
139where
140    Input: DeserializeOwned + Send,
141{
142    async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
143        let stated =
144            parts.headers.iter().filter_map(|(name, value)| Some((name.to_string(), value.to_str().ok()?.to_owned())));
145
146        headers_of(stated).map_err(|error| (StatusCode::BAD_REQUEST, error).into_response())
147    }
148}
149
150/// Marks an argument read from a body arriving as parts.
151pub struct AxumMultipartInput<Input>(PhantomData<Input>);
152
153impl<Input> AxumInputAlg<Input> for AxumMultipartInput<Input>
154where
155    Input: FromPartsAlg<AxumParts> + Send,
156    Input::Error: Display,
157{
158    async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
159        let request = taken(parts, body);
160        let read = Multipart::from_request(request, &()).await.map_err(IntoResponse::into_response)?;
161
162        Input::from_parts(AxumParts(read))
163            .await
164            .map_err(|error| (StatusCode::BAD_REQUEST, error.to_string()).into_response())
165    }
166}
167
168/// Reads the whole argument product one endpoint states, in declaration order.
169pub(crate) trait AxumInputsAlg<Outputs> {
170    fn extract(parts: &mut Parts, body: &mut Option<Body>) -> impl Future<Output = Result<Outputs, Response>> + Send;
171}
172
173impl AxumInputsAlg<()> for () {
174    async fn extract(_parts: &mut Parts, _body: &mut Option<Body>) -> Result<(), Response> {
175        Ok(())
176    }
177}
178
179macro_rules! axum_inputs {
180    ($($input:ident => $output:ident),+ $(,)?) => {
181        impl<$($input, $output),+> AxumInputsAlg<($($output,)+)> for ($($input,)+)
182        where
183            $($input: AxumInputAlg<$output>, $output: Send,)+
184        {
185            async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<($($output,)+), Response> {
186                Ok(($($input::extract(parts, body).await?,)+))
187            }
188        }
189    };
190}
191
192axum_inputs!(I1 => O1);
193axum_inputs!(I1 => O1, I2 => O2);
194axum_inputs!(I1 => O1, I2 => O2, I3 => O3);
195axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4);
196axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5);
197axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6);
198axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7);
199axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8);
200axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9);
201axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10);
202axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11);
203axum_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);
204axum_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);
205axum_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);
206axum_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);
207axum_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);