1use core::mem;
2use core::{convert::Infallible, future::Future};
3
4#[cfg(feature = "openapi")]
5use crate::openapi::{ExtractorSchema, SchemaRef};
6use alloc::boxed::Box;
7#[cfg(feature = "openapi")]
8use alloc::collections::BTreeMap;
9use http_kit::error::BoxHttpError;
10use http_kit::{
11 http_error,
12 utils::{ByteStr, Bytes},
13 Body, HttpError, Method, Request, StatusCode, Uri,
14};
15
16pub trait Extractor: Sized + Send + Sync + 'static {
18 type Error: HttpError;
20 fn extract(request: &mut Request) -> impl Future<Output = Result<Self, Self::Error>> + Send;
22
23 #[cfg(feature = "openapi")]
25 #[must_use]
26 fn openapi() -> Option<ExtractorSchema> {
27 None
28 }
29
30 #[cfg(feature = "openapi")]
32 fn register_openapi_schemas(_defs: &mut BTreeMap<String, SchemaRef>) {}
33}
34
35macro_rules! impl_tuple_extractor {
36 ($($ty:ident),*) => {
37 const _:() = {
38 #[doc(hidden)]
40 pub enum TupleExtractorError<$($ty:Extractor),*> {
41 $($ty(<$ty as Extractor>::Error),)*
42 }
43
44 impl <$($ty: Extractor),*>core::fmt::Display for TupleExtractorError<$($ty),*> {
45 #[allow(unused_variables)]
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 match self {
48 $(TupleExtractorError::$ty(e) => write!(f,"{}",e),)*
49 #[allow(unreachable_patterns)]
50 _ => unreachable!(),
51 }
52 }
53 }
54
55 impl <$($ty: Extractor),*>core::fmt::Debug for TupleExtractorError<$($ty),*> {
56 #[allow(unused_variables)]
57 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58 match self {
59 $(TupleExtractorError::$ty(e) => write!(f,"{:?}",e),)*
60 #[allow(unreachable_patterns)]
61 _ => unreachable!(),
62 }
63 }
64 }
65
66 impl <$($ty: Extractor),*>core::error::Error for TupleExtractorError<$($ty),*> {}
67
68 impl <$($ty: Extractor),*>http_kit::HttpError for TupleExtractorError<$($ty),*> {
69 fn status(&self) -> http_kit::StatusCode {
70 match self {
71 $(TupleExtractorError::$ty(e) => e.status(),)*
72 #[allow(unreachable_patterns)]
73 _ => unreachable!(),
74 }
75 }
76 }
77
78
79 #[allow(non_snake_case)]
80 #[allow(unused_variables)]
81 #[allow(clippy::unused_unit)]
82 impl<$($ty:Extractor+Send,)*> Extractor for ($($ty,)*) {
83 type Error = TupleExtractorError<$($ty),*>;
84 async fn extract(request:&mut Request) -> Result<Self,Self::Error>{
85 Ok(($($ty::extract(request).await.map_err(|error|{
86 TupleExtractorError::$ty(error)
87 })?,)*))
88 }
89 }
90
91 #[cfg(feature = "openapi")]
92 #[allow(dead_code)]
93 const fn openapi() -> Option<crate::openapi::ExtractorSchema> {
94 None
95 }
96
97 #[cfg(feature = "openapi")]
98 #[allow(dead_code)]
99 const fn register_openapi_schemas(
100 _defs: &mut alloc::collections::BTreeMap<String, crate::openapi::SchemaRef>,
101 ) {
102 }
103 };
104 };
105}
106
107tuples!(impl_tuple_extractor);
108
109http_error!(pub InvalidBody, StatusCode::BAD_REQUEST, "Failed to read request body");
110
111impl Extractor for Bytes {
112 type Error = InvalidBody;
113 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
114 let body = mem::replace(request.body_mut(), Body::empty());
115 body.into_bytes().await.map_err(|_| InvalidBody::new())
116 }
117
118 #[cfg(feature = "openapi")]
119 fn openapi() -> Option<ExtractorSchema> {
120 Some(ExtractorSchema {
121 content_type: Some("application/octet-stream"),
122 schema: None,
123 })
124 }
125}
126
127impl Extractor for ByteStr {
128 type Error = InvalidBody;
129 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
130 let body = mem::replace(request.body_mut(), Body::empty());
131 body.into_string().await.map_err(|_| InvalidBody::new())
132 }
133
134 #[cfg(feature = "openapi")]
135 fn openapi() -> Option<ExtractorSchema> {
136 Some(ExtractorSchema {
137 content_type: Some("text/plain; charset=utf-8"),
138 schema: None,
139 })
140 }
141}
142
143impl Extractor for Body {
144 type Error = Infallible;
145 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
146 Ok(mem::replace(request.body_mut(), Self::empty()))
147 }
148
149 #[cfg(feature = "openapi")]
150 fn openapi() -> Option<ExtractorSchema> {
151 Some(ExtractorSchema {
152 content_type: Some("application/octet-stream"),
153 schema: None,
154 })
155 }
156}
157
158impl Extractor for Uri {
159 type Error = Infallible;
160 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
161 Ok(request.uri().clone())
162 }
163}
164
165impl Extractor for Method {
166 type Error = Infallible;
167 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
168 Ok(request.method().clone())
169 }
170}
171
172impl<T: Extractor> Extractor for Option<T> {
173 type Error = Infallible;
174 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
175 Ok(T::extract(request).await.ok())
176 }
177
178 #[cfg(feature = "openapi")]
179 fn openapi() -> Option<ExtractorSchema> {
180 T::openapi()
181 }
182
183 #[cfg(feature = "openapi")]
184 fn register_openapi_schemas(defs: &mut BTreeMap<String, SchemaRef>) {
185 T::register_openapi_schemas(defs);
186 }
187}
188
189impl<T: Extractor> Extractor for Result<T, BoxHttpError> {
191 type Error = Infallible;
192 async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
193 Ok(T::extract(request)
194 .await
195 .map_err(|e| Box::new(e) as BoxHttpError))
196 }
197
198 #[cfg(feature = "openapi")]
199 fn openapi() -> Option<ExtractorSchema> {
200 T::openapi()
201 }
202
203 #[cfg(feature = "openapi")]
204 fn register_openapi_schemas(defs: &mut BTreeMap<String, SchemaRef>) {
205 T::register_openapi_schemas(defs);
206 }
207}