skyzen_core/
extract.rs

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
16/// Extract a object from request,always is the header,body value,etc.
17pub trait Extractor: Sized + Send + Sync + 'static {
18    /// Error type returned when extraction fails.
19    type Error: HttpError;
20    /// Read the request and parse a value.
21    fn extract(request: &mut Request) -> impl Future<Output = Result<Self, Self::Error>> + Send;
22
23    /// Describe the extractor's `OpenAPI` schema, if available.
24    #[cfg(feature = "openapi")]
25    #[must_use]
26    fn openapi() -> Option<ExtractorSchema> {
27        None
28    }
29
30    /// Register dependent schemas into the `OpenAPI` components map.
31    #[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            // To prevent these macro-generated errors from overwhelming users.
39            #[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
189// Let's erase the error for Result<T,E>, otherwise user have to deal with double error types.
190impl<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}