1use axum::{
2 async_trait,
3 body::HttpBody,
4 extract::FromRequest,
5 http::{header, HeaderValue, Request, StatusCode},
6 response::{IntoResponse, Response},
7 BoxError,
8};
9
10const TOML_MIME: &str = "application/toml";
11const TEXT_UTF8_MIME: &str = "text/plain; charset=utf-8";
12
13#[derive(Debug, thiserror::Error)]
14pub enum TomlRejection {
15 #[error("Failed to deserialize the request body")]
16 DeserializationError(#[from] toml::de::Error),
17 #[error("Request body didn't contain valid UTF-8")]
18 StringRejection(#[from] axum::extract::rejection::StringRejection),
19}
20
21impl IntoResponse for TomlRejection {
22 fn into_response(self) -> Response {
23 match self {
24 Self::DeserializationError(err) => (
25 StatusCode::BAD_REQUEST,
26 [(
27 header::CONTENT_TYPE,
28 HeaderValue::from_static(TEXT_UTF8_MIME),
29 )],
30 err.to_string(),
31 )
32 .into_response(),
33 Self::StringRejection(err) => err.into_response(),
34 }
35 }
36}
37
38pub struct Toml<T>(pub T);
39
40#[async_trait]
41impl<T, S, B> FromRequest<S, B> for Toml<T>
42where
43 T: serde::de::DeserializeOwned,
44 B: HttpBody + Send + 'static,
45 B::Data: Send,
46 B::Error: Into<BoxError>,
47 S: Send + Sync,
48{
49 type Rejection = TomlRejection;
50
51 async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
52 let text = String::from_request(req, state).await?;
53 Ok(Toml(toml::from_str(&text)?))
54 }
55}
56
57impl<T> IntoResponse for Toml<T>
58where
59 T: serde::Serialize,
60{
61 fn into_response(self) -> Response {
62 match toml::to_string(&self.0) {
63 Ok(serialized) => (
64 StatusCode::OK,
65 [(header::CONTENT_TYPE, HeaderValue::from_static(TOML_MIME))],
66 serialized,
67 )
68 .into_response(),
69 Err(err) => (
70 StatusCode::INTERNAL_SERVER_ERROR,
71 [(
72 header::CONTENT_TYPE,
73 HeaderValue::from_static(TEXT_UTF8_MIME),
74 )],
75 err.to_string(),
76 )
77 .into_response(),
78 }
79 }
80}