rama_http/service/web/endpoint/response/
form.rs

1use std::fmt;
2
3use super::IntoResponse;
4use crate::Body;
5use crate::Response;
6use crate::dep::http::StatusCode;
7use crate::headers::ContentType;
8use rama_core::error::OpaqueError;
9use rama_utils::macros::impl_deref;
10use serde::Serialize;
11
12use super::Headers;
13
14/// Wrapper used to create Form Http [`Response`]s,
15/// as well as to extract Form from Http [`Request`] bodies.
16///
17/// [`Request`]: crate::Request
18/// [`Response`]: crate::Response
19///
20/// # Examples
21/// ## Creating a Form Response
22///
23/// ```
24/// use serde::Serialize;
25/// use rama_http::service::web::response::{
26///     IntoResponse, Form,
27/// };
28///
29/// #[derive(Serialize)]
30/// struct Payload {
31///     name: String,
32///     age: i32,
33///     is_student: bool
34/// }
35///
36/// async fn handler() -> impl IntoResponse {
37///     Form(Payload {
38///         name: "john".to_string(),
39///         age: 30,
40///         is_student: false
41///     })
42/// }
43/// ```
44///
45/// ## Extracting Form from a Request
46///
47/// ```
48/// use rama_http::service::web::response::Form;
49///
50/// #[derive(Debug, serde::Deserialize)]
51/// struct Input {
52///     name: String,
53///     age: u8,
54///     alive: Option<bool>,
55/// }
56///
57/// # fn bury(name: impl AsRef<str>) {}
58///
59/// async fn handler(Form(input): Form<Input>) {
60///     if !input.alive.unwrap_or_default() {
61///         bury(&input.name);
62///     }
63/// }
64/// ```
65pub struct Form<T>(pub T);
66
67impl<T: fmt::Debug> fmt::Debug for Form<T> {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.debug_tuple("Form").field(&self.0).finish()
70    }
71}
72
73impl<T: Clone> Clone for Form<T> {
74    fn clone(&self) -> Self {
75        Self(self.0.clone())
76    }
77}
78
79impl_deref!(Form);
80
81impl<T> From<T> for Form<T> {
82    fn from(value: T) -> Self {
83        Self(value)
84    }
85}
86
87impl<T> IntoResponse for Form<T>
88where
89    T: Serialize,
90{
91    fn into_response(self) -> Response {
92        // Extracted into separate fn so it's only compiled once for all T.
93        fn make_response(ser_result: Result<String, serde_html_form::ser::Error>) -> Response {
94            match ser_result {
95                Ok(body) => {
96                    (Headers::single(ContentType::form_url_encoded()), body).into_response()
97                }
98                Err(err) => {
99                    tracing::error!(error = %err, "response error");
100                    StatusCode::INTERNAL_SERVER_ERROR.into_response()
101                }
102            }
103        }
104        make_response(serde_html_form::to_string(&self.0))
105    }
106}
107
108impl<T> TryInto<Body> for Form<T>
109where
110    T: Serialize,
111{
112    type Error = OpaqueError;
113
114    fn try_into(self) -> Result<Body, Self::Error> {
115        match serde_html_form::to_string(&self.0) {
116            Ok(body) => Ok(body.into()),
117            Err(err) => Err(OpaqueError::from_std(err)),
118        }
119    }
120}