predawn/payload/
form.rs

1use std::{collections::BTreeMap, convert::Infallible};
2
3use bytes::Bytes;
4use http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
5use mime::{APPLICATION, WWW_FORM_URLENCODED};
6use predawn_core::{
7    api_request::ApiRequest,
8    api_response::ApiResponse,
9    body::RequestBody,
10    from_request::FromRequest,
11    impl_deref,
12    into_response::IntoResponse,
13    media_type::{
14        has_media_type, MediaType, MultiRequestMediaType, MultiResponseMediaType, RequestMediaType,
15        ResponseMediaType, SingleMediaType,
16    },
17    openapi::{self, Parameter, Schema},
18    request::Head,
19    response::{MultiResponse, Response, SingleResponse},
20};
21use predawn_schema::ToSchema;
22use serde::{de::DeserializeOwned, Serialize};
23use snafu::ResultExt;
24
25use crate::response_error::{
26    DeserializeFormSnafu, InvalidFormContentTypeSnafu, ReadFormBytesSnafu, ReadFormError,
27    WriteFormError, WriteFormSnafu,
28};
29
30#[derive(Debug, Default, Clone, Copy)]
31pub struct Form<T>(pub T);
32
33impl_deref!(Form);
34
35impl<'a, T> FromRequest<'a> for Form<T>
36where
37    T: DeserializeOwned,
38{
39    type Error = ReadFormError;
40
41    async fn from_request(head: &'a mut Head, body: RequestBody) -> Result<Self, Self::Error> {
42        let content_type = head.content_type().unwrap_or_default();
43
44        if <Self as RequestMediaType>::check_content_type(content_type) {
45            let bytes = Bytes::from_request(head, body)
46                .await
47                .context(ReadFormBytesSnafu)?;
48
49            let form = crate::util::deserialize_form(&bytes).context(DeserializeFormSnafu)?;
50            Ok(Form(form))
51        } else {
52            InvalidFormContentTypeSnafu.fail()
53        }
54    }
55}
56
57impl<T: ToSchema> ApiRequest for Form<T> {
58    fn parameters(_: &mut BTreeMap<String, Schema>, _: &mut Vec<String>) -> Option<Vec<Parameter>> {
59        None
60    }
61
62    fn request_body(
63        schemas: &mut BTreeMap<String, Schema>,
64        schemas_in_progress: &mut Vec<String>,
65    ) -> Option<openapi::RequestBody> {
66        Some(openapi::RequestBody {
67            content: <Self as MultiRequestMediaType>::content(schemas, schemas_in_progress),
68            required: true,
69            ..Default::default()
70        })
71    }
72}
73
74impl<T> IntoResponse for Form<T>
75where
76    T: Serialize,
77{
78    type Error = WriteFormError;
79
80    fn into_response(self) -> Result<Response, Self::Error> {
81        let mut response = crate::util::serialize_form(&self.0)
82            .context(WriteFormSnafu)?
83            .into_response()
84            .unwrap_or_else(|a: Infallible| match a {});
85
86        response.headers_mut().insert(
87            CONTENT_TYPE,
88            HeaderValue::from_static(<Self as MediaType>::MEDIA_TYPE),
89        );
90
91        Ok(response)
92    }
93}
94
95impl<T: ToSchema> ApiResponse for Form<T> {
96    fn responses(
97        schemas: &mut BTreeMap<String, Schema>,
98        schemas_in_progress: &mut Vec<String>,
99    ) -> Option<BTreeMap<StatusCode, openapi::Response>> {
100        Some(<Self as MultiResponse>::responses(
101            schemas,
102            schemas_in_progress,
103        ))
104    }
105}
106
107impl<T> MediaType for Form<T> {
108    const MEDIA_TYPE: &'static str = "application/x-www-form-urlencoded";
109}
110
111impl<T> RequestMediaType for Form<T> {
112    fn check_content_type(content_type: &str) -> bool {
113        has_media_type(
114            content_type,
115            APPLICATION.as_str(),
116            WWW_FORM_URLENCODED.as_str(),
117            WWW_FORM_URLENCODED.as_str(),
118            None,
119        )
120    }
121}
122
123impl<T> ResponseMediaType for Form<T> {}
124
125impl<T: ToSchema> SingleMediaType for Form<T> {
126    fn media_type(
127        schemas: &mut BTreeMap<String, Schema>,
128        schemas_in_progress: &mut Vec<String>,
129    ) -> openapi::MediaType {
130        openapi::MediaType {
131            schema: Some(T::schema_ref(schemas, schemas_in_progress)),
132            ..Default::default()
133        }
134    }
135}
136
137impl<T: ToSchema> SingleResponse for Form<T> {
138    fn response(
139        schemas: &mut BTreeMap<String, Schema>,
140        schemas_in_progress: &mut Vec<String>,
141    ) -> openapi::Response {
142        openapi::Response {
143            content: <Self as MultiResponseMediaType>::content(schemas, schemas_in_progress),
144            ..Default::default()
145        }
146    }
147}