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