1#![doc = include_str!("../README.md")]
2use axum::{
17 body::Bytes,
18 extract::{FromRequest, Request},
19 response::{IntoResponse, Response},
20};
21use hyper::{HeaderMap, StatusCode};
22use raxb::de::XmlDeserialize;
23use thiserror::Error;
24
25#[derive(Debug, Clone, Default)]
26#[must_use]
27pub struct RaxbXml<T>(pub T);
28
29#[async_trait::async_trait]
30impl<T, S> FromRequest<S> for RaxbXml<T>
31where
32 T: XmlDeserialize,
33 S: Send + Sync,
34{
35 type Rejection = RaxbXmlRejection;
36
37 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
38 if xml_content_type(req.headers()) {
39 let bytes = Bytes::from_request(req, state).await?;
40 RaxbXmlSource::from_bytes(bytes).map(|RaxbXmlSource(xml, _)| Self(xml))
41 } else {
42 Err(MissingXmlContentType.into())
43 }
44 }
45}
46
47#[derive(Debug, Clone, Default)]
48#[must_use]
49pub struct RaxbXmlSource<T>(pub T, pub Bytes);
50
51#[async_trait::async_trait]
52impl<T, S> FromRequest<S> for RaxbXmlSource<T>
53where
54 T: XmlDeserialize,
55 S: Send + Sync,
56{
57 type Rejection = RaxbXmlRejection;
58
59 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
60 if xml_content_type(req.headers()) {
61 let bytes = Bytes::from_request(req, state).await?;
62 Self::from_bytes(bytes)
63 } else {
64 Err(MissingXmlContentType.into())
65 }
66 }
67}
68
69impl<T> RaxbXmlSource<T>
70where
71 T: XmlDeserialize,
72{
73 pub fn from_bytes(bytes: Bytes) -> Result<Self, RaxbXmlRejection> {
77 raxb::de::from_reader(&*bytes)
78 .map(|xml| Self(xml, bytes))
79 .map_err(From::from)
80 }
81}
82
83#[derive(Debug, Error)]
84pub enum RaxbXmlRejection {
85 #[error(transparent)]
86 MissingXmlContentType(#[from] MissingXmlContentType),
87 #[error(transparent)]
88 AxumError(#[from] axum::extract::rejection::BytesRejection),
89 #[error(transparent)]
90 RaxbError(#[from] raxb::de::XmlDeserializeError),
91}
92
93impl IntoResponse for RaxbXmlRejection {
94 fn into_response(self) -> Response {
95 StatusCode::BAD_REQUEST.into_response()
96 }
97}
98
99fn xml_content_type(headers: &HeaderMap) -> bool {
100 let content_type = if let Some(content_type) = headers.get(hyper::header::CONTENT_TYPE) {
101 content_type
102 } else {
103 return false;
104 };
105
106 let content_type = if let Ok(content_type) = content_type.to_str() {
107 content_type
108 } else {
109 return false;
110 };
111
112 let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
113 mime
114 } else {
115 return false;
116 };
117
118 let is_xml_content_type = mime.type_() == "application"
119 && (mime.subtype() == "xml" || mime.suffix().is_some_and(|name| name == "xml"));
120
121 is_xml_content_type
122}
123
124#[derive(Debug, Clone, Copy, Default, Error)]
125#[error("missing or wrong content-type, must be application/xml")]
126pub struct MissingXmlContentType;
127
128impl IntoResponse for MissingXmlContentType {
129 fn into_response(self) -> Response {
130 StatusCode::BAD_REQUEST.into_response()
131 }
132}