Skip to main content

ayun_server/request/
mod.rs

1use crate::traits::RequestTrait;
2
3#[cfg(feature = "request-validate")]
4pub use validate::Validate;
5
6#[cfg(feature = "request-validate")]
7mod validate;
8
9#[async_trait::async_trait]
10impl RequestTrait for axum::extract::Request {
11    fn content_type(&self) -> mime::Mime {
12        if has_content_type(self.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
13            return mime::APPLICATION_WWW_FORM_URLENCODED;
14        }
15
16        if has_content_type(self.headers(), &mime::APPLICATION_JSON) {
17            return mime::APPLICATION_JSON;
18        }
19
20        if has_content_type(self.headers(), &mime::MULTIPART_FORM_DATA) {
21            return mime::MULTIPART_FORM_DATA;
22        }
23
24        mime::TEXT_PLAIN
25    }
26
27    fn header<K>(&self, key: K) -> Option<&str>
28    where
29        K: axum::http::header::AsHeaderName,
30    {
31        self.headers()
32            .get(key)
33            .and_then(|value| value.to_str().ok())
34    }
35
36    fn user_agent(&self) -> &str {
37        self.headers()
38            .get(axum::http::header::USER_AGENT)
39            .map_or("", |h| h.to_str().unwrap_or(""))
40    }
41
42    async fn path<T: serde::de::DeserializeOwned + Send + 'static>(
43        &mut self,
44    ) -> Result<T, axum::extract::rejection::PathRejection> {
45        use axum::{extract::Path, RequestExt};
46
47        let Path(path) = self.extract_parts::<Path<T>>().await?;
48
49        Ok(path)
50    }
51
52    #[cfg(feature = "request-query")]
53    async fn query<T: serde::de::DeserializeOwned + 'static>(
54        &mut self,
55    ) -> crate::ServerResult<T, axum::extract::rejection::QueryRejection> {
56        use axum::{extract::Query, RequestExt};
57
58        let Query(query) = self.extract_parts::<Query<T>>().await?;
59
60        Ok(query)
61    }
62
63    #[cfg(feature = "request-form")]
64    async fn form<T: serde::de::DeserializeOwned>(
65        self,
66    ) -> crate::ServerResult<T, axum::extract::rejection::FormRejection> {
67        use axum::{extract::FromRequest, Form};
68        let Form(payload) = Form::<T>::from_request(self, &()).await?;
69
70        Ok(payload)
71    }
72
73    #[cfg(feature = "request-json")]
74    async fn json<T: serde::de::DeserializeOwned>(
75        self,
76    ) -> crate::ServerResult<T, axum::extract::rejection::JsonRejection> {
77        use axum::{extract::FromRequest, Json};
78        let Json(payload) = Json::<T>::from_request(self, &()).await?;
79
80        Ok(payload)
81    }
82
83    #[cfg(feature = "request-multipart")]
84    async fn multipart<T: serde::de::DeserializeOwned>(self) -> crate::ServerResult<T> {
85        use axum::extract::{FromRequest, Multipart};
86        let mut multipart = Multipart::from_request(self, &()).await?;
87
88        let mut data = serde_json::Map::new();
89
90        while let Some(field) = multipart.next_field().await? {
91            if let Some(name) = field.name() {
92                if let Some(file_name) = field.file_name() {
93                    data.insert(name.to_owned(), file_name.into());
94                } else {
95                    data.insert(name.to_owned(), field.text().await?.into());
96                }
97            }
98        }
99
100        let payload = serde_json::from_value::<T>(data.into())?;
101
102        Ok(payload)
103    }
104
105    #[cfg(feature = "request-multipart")]
106    async fn file(self, key: &str) -> crate::ServerResult<Option<axum::body::Bytes>> {
107        use axum::extract::{FromRequest, Multipart};
108        let mut multipart = Multipart::from_request(self, &()).await?;
109
110        while let Some(field) = multipart.next_field().await? {
111            if let Some(name) = field.name() {
112                if field.file_name().is_some() && name == key {
113                    return Ok(Some(field.bytes().await?));
114                }
115            }
116        }
117
118        Ok(None)
119    }
120
121    #[cfg(feature = "request-validate")]
122    async fn validate<T: serde::de::DeserializeOwned + validator::Validate>(
123        self,
124    ) -> crate::ServerResult<T> {
125        let payload: T = if self.content_type() == mime::APPLICATION_JSON {
126            self.json::<T>().await?
127        } else if self.content_type() == mime::APPLICATION_WWW_FORM_URLENCODED {
128            self.form::<T>().await?
129        } else if self.content_type() == mime::MULTIPART_FORM_DATA {
130            self.multipart::<T>().await?
131        } else {
132            return Err(crate::error::Error::UnsupportedMediaType);
133        };
134
135        payload.validate()?;
136
137        Ok(payload)
138    }
139
140    #[cfg(feature = "request-id")]
141    fn id(&self) -> Option<&str> {
142        self.header(crate::config::X_REQUEST_ID)
143    }
144
145    #[cfg(feature = "request-auth")]
146    fn user(&self) -> Option<ayun_auth::support::UserClaims> {
147        self.extensions()
148            .get::<ayun_auth::support::UserClaims>()
149            .cloned()
150    }
151
152    #[cfg(feature = "request-auth")]
153    fn token(&self) -> crate::ServerResult<String> {
154        let config = ayun_config::support::config::<ayun_auth::config::Auth>("auth").expect(
155            "failed to
156    get jwt config",
157        );
158
159        match config
160            .jwt
161            .location
162            .as_ref()
163            .unwrap_or(&ayun_auth::config::JwtLocation::Bearer)
164        {
165            ayun_auth::config::JwtLocation::Query { name } => {
166                Ok(extract_token_from_query(name, self.uri())?)
167            }
168            ayun_auth::config::JwtLocation::Cookie { name } => Ok(extract_token_from_cookie(
169                name,
170                &axum_extra::extract::cookie::CookieJar::from_headers(self.headers()),
171            )?),
172            ayun_auth::config::JwtLocation::Bearer => Ok(extract_token_from_header(self.headers())
173                .map_err(|e| ayun_auth::error::Error::Unauthorized(e.to_string()))?),
174        }
175    }
176}
177
178fn has_content_type(
179    headers: &axum::http::header::HeaderMap,
180    expected_content_type: &mime::Mime,
181) -> bool {
182    let content_type = if let Some(content_type) = headers.get(axum::http::header::CONTENT_TYPE) {
183        content_type
184    } else {
185        return false;
186    };
187
188    let content_type = if let Ok(content_type) = content_type.to_str() {
189        content_type
190    } else {
191        return false;
192    };
193
194    content_type.starts_with(expected_content_type.as_ref())
195}
196
197#[cfg(feature = "request-auth")]
198fn extract_token_from_header(
199    headers: &axum::http::header::HeaderMap,
200) -> crate::ServerResult<String> {
201    use axum::http::header::AUTHORIZATION;
202    use ayun_auth::error::Error;
203
204    Ok(headers
205        .get(AUTHORIZATION)
206        .ok_or(Error::Unauthorized(format!(
207            "header {} token not found",
208            AUTHORIZATION
209        )))?
210        .to_str()
211        .map_err(|err| Error::Unauthorized(err.to_string()))?
212        .strip_prefix("Bearer ")
213        .ok_or(Error::Unauthorized(format!(
214            "error strip {} value",
215            AUTHORIZATION
216        )))?
217        .to_string())
218}
219
220#[cfg(feature = "request-auth")]
221fn extract_token_from_cookie(
222    name: &str,
223    jar: &axum_extra::extract::cookie::CookieJar,
224) -> crate::ServerResult<String> {
225    use ayun_auth::error::Error;
226
227    Ok(jar
228        .get(name)
229        .ok_or(Error::Unauthorized("token is not found".to_string()))?
230        .to_string()
231        .strip_prefix(&format!("{name}="))
232        .ok_or(Error::Unauthorized("error strip value".to_string()))?
233        .to_string())
234}
235
236#[cfg(feature = "request-auth")]
237fn extract_token_from_query(name: &str, uri: &axum::http::Uri) -> crate::ServerResult<String> {
238    use axum::extract::Query;
239    use ayun_auth::error::Error;
240    use std::collections::HashMap;
241
242    let parameters: Query<HashMap<String, String>> =
243        Query::try_from_uri(uri).map_err(|err| Error::Unauthorized(err.to_string()))?;
244
245    Ok(parameters
246        .get(name)
247        .cloned()
248        .ok_or(Error::Unauthorized(format!(
249            "`{name}` query parameter not found"
250        )))?)
251}