rustcms_axum_xml/
lib.rs

1use axum_core::{
2    extract::{FromRequest},
3    BoxError,
4};
5use bytes::Bytes;
6use http_body::Body as HttpBody;
7use async_trait::async_trait;
8use axum_core::response::{IntoResponse, Response};
9use http::{
10    header::{self, HeaderMap, HeaderValue},
11    Request, StatusCode,
12};
13use serde::{de::DeserializeOwned, Serialize};
14use std::ops::{Deref, DerefMut};
15
16use crate::rejection::XmlRejection;
17
18mod rejection;
19#[cfg(test)]
20mod tests;
21
22/// XML Extractor / Response.
23///
24/// When used as an extractor, it can deserialize request bodies into some type that
25/// implements [`serde::Deserialize`]. The request will be rejected (and a [`XmlRejection`] will
26/// be returned) if:
27///
28/// - The request doesn't have a `Content-Type: application/xml` (or similar) header.
29/// - The body doesn't contain syntactically valid XML.
30/// - The body contains syntactically valid XML but it couldn't be deserialized into the target
31/// type.
32/// - Buffering the request body fails.
33///
34/// Since parsing XML requires consuming the request body, the `Xml` extractor must be
35/// *last* if there are multiple extractors in a handler.
36/// See ["the order of extractors"][order-of-extractors]
37///
38/// [order-of-extractors]: crate::extract#the-order-of-extractors
39///
40/// See [`XmlRejection`] for more details.
41///
42/// # Extractor example
43///
44/// ```rust,no_run
45/// use axum::{
46///     extract,
47///     routing::post,
48///     Router,
49/// };
50/// use serde::Deserialize;
51/// use rustcms_axum_xml::Xml;
52///
53/// #[derive(Deserialize)]
54/// struct CreateUser {
55///     email: String,
56///     password: String,
57/// }
58///
59/// async fn create_user(Xml(payload): Xml<CreateUser>) {
60///     // payload is a `CreateUser`
61/// }
62///
63/// let app = Router::new().route("/users", post(create_user));
64/// # async {
65/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
66/// # };
67/// ```
68///
69/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to
70/// `XML`, and will automatically set `Content-Type: application/xml` header.
71///
72/// # Response example
73///
74/// ```
75/// use axum::{
76///     extract::Path,
77///     routing::get,
78///     Router,
79/// };
80/// use serde::Serialize;
81/// use uuid::Uuid;
82/// use rustcms_axum_xml::Xml;
83///
84/// #[derive(Serialize)]
85/// struct User {
86///     id: Uuid,
87///     username: String,
88/// }
89///
90/// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> {
91///     let user = find_user(user_id).await;
92///     Xml(user)
93/// }
94///
95/// async fn find_user(user_id: Uuid) -> User {
96///     // ...
97///     # unimplemented!()
98/// }
99///
100/// let app = Router::new().route("/users/:id", get(get_user));
101/// # async {
102/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
103/// # };
104/// ```
105#[derive(Debug, Clone, Copy, Default)]
106pub struct Xml<T>(pub T);
107
108#[async_trait]
109impl<T, S, B> FromRequest<S, B> for Xml<T>
110where
111    T: DeserializeOwned,
112    B: HttpBody + Send + 'static,
113    B::Data: Send,
114    B::Error: Into<BoxError>,
115    S: Send + Sync,
116{
117    type Rejection = XmlRejection;
118
119    async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
120
121        if xml_content_type(req.headers()) {
122            let bytes = Bytes::from_request(req, state).await?;
123
124            let value = quick_xml::de::from_reader(&*bytes)?;
125
126            Ok(Self(value))
127        } else {
128            Err(XmlRejection::MissingXMLContentType)
129        }
130
131    }
132}
133
134fn xml_content_type(headers: &HeaderMap) -> bool {
135    let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
136        content_type
137    } else {
138        return false;
139    };
140
141    let content_type = if let Ok(content_type) = content_type.to_str() {
142        content_type
143    } else {
144        return false;
145    };
146
147    let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
148        mime
149    } else {
150        return false;
151    };
152
153    let is_xml_content_type =  (mime.type_() == "application" || mime.type_() == "text")
154        && (mime.subtype() == "xml" || mime.suffix().map_or(false, |name| name == "xml"));
155
156    is_xml_content_type
157}
158
159impl<T> Deref for Xml<T> {
160    type Target = T;
161
162    fn deref(&self) -> &Self::Target {
163        &self.0
164    }
165}
166
167impl<T> DerefMut for Xml<T> {
168    fn deref_mut(&mut self) -> &mut Self::Target {
169        &mut self.0
170    }
171}
172
173impl<T> From<T> for Xml<T> {
174    fn from(inner: T) -> Self {
175        Self(inner)
176    }
177}
178
179impl<T> IntoResponse for Xml<T>
180where
181    T: Serialize,
182{
183    fn into_response(self) -> Response {
184        let mut bytes = Vec::new();
185        match quick_xml::se::to_writer(&mut bytes, &self.0) {
186            Ok(_) => (
187                [(
188                    header::CONTENT_TYPE,
189                    HeaderValue::from_static("application/xml"),
190                )],
191                bytes,
192            )
193                .into_response(),
194            Err(err) => (
195                StatusCode::INTERNAL_SERVER_ERROR,
196                [(
197                    header::CONTENT_TYPE,
198                    HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
199                )],
200                err.to_string(),
201            )
202                .into_response(),
203        }
204    }
205}