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}