diff --git a/examples/seeded_extractor.rs b/examples/seeded_extractor.rs new file mode 100644 index 000000000..71caf5bb3 --- /dev/null +++ b/examples/seeded_extractor.rs @@ -0,0 +1,20 @@ +#![feature(async_await, futures_api)] + +use tide::Seeded; +use tide::head::{NamedHeader, Header, SegmentName, Named}; +use http::header::{HeaderName, HeaderValue}; + +async fn display_header(value: Header) -> String { + String::from_utf8_lossy(value.0.as_ref()).into_owned() +} + +async fn display_number(nr: Named) -> String { + format!("Segment number: {}", nr.0) +} + +fn main() { + let mut app = tide::App::new(()); + app.at("/").get(Seeded(display_header, NamedHeader(HeaderName::from_static("user-agent")))); + app.at("/numbered/{num}").get(Seeded(display_number, SegmentName("num".into()))); + app.serve(); +} diff --git a/src/endpoint.rs b/src/endpoint.rs index 6b3c9295c..6098037ab 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -1,7 +1,7 @@ use futures::future::{Future, FutureObj}; use crate::{ - configuration::Store, extract::Extract, head::Head, IntoResponse, Request, Response, RouteMatch, + configuration::Store, Extract, ExtractSeed, head::Head, IntoResponse, Request, Response, RouteMatch, }; /// The raw representation of an endpoint. @@ -119,6 +119,44 @@ macro_rules! call_f { }; } +pub struct Seeded(pub F, pub E); + +macro_rules! seeded_end_point_impl_raw { + ($([$head:ty])* $(($X:ident,$Y:ident)),*) => { + impl Endpoint, $($head,)* $(Ty<$X>),*)> for Seeded + where + T: Send + Sync + Clone + 'static + Fn($($head,)* $($X),*) -> Fut, + Data: Send + Sync + Clone + 'static, + Fut: Future + Send + 'static, + Fut::Output: IntoResponse, + $( + $X: Send + Sized + 'static, + $Y: ExtractSeed<$X, Data> + ),* + { + type Fut = FutureObj<'static, Response>; + + #[allow(unused_mut, unused_parens, non_snake_case)] + fn call(&self, mut data: Data, mut req: Request, params: Option>, store: &Store) -> Self::Fut { + let f = self.0.clone(); + let ($($Y),*) = &self.1; + $(let $X = <$Y as ExtractSeed<$X, Data>>::extract($Y, &mut data, &mut req, ¶ms, store);)* + FutureObj::new(Box::new(async move { + let (parts, _) = req.into_parts(); + let head = Head::from(parts); + $(let $X = match await!($X) { + Ok(x) => x, + Err(resp) => return resp, + };)* + let res = await!(call_f!($($head;)* (f, head); $($X),*)); + + res.into_response() + })) + } + } + }; +} + macro_rules! end_point_impl_raw { ($([$head:ty])* $($X:ident),*) => { impl Endpoint, $($head,)* $(Ty<$X>),*)> for T @@ -128,6 +166,7 @@ macro_rules! end_point_impl_raw { Fut: Future + Send + 'static, Fut::Output: IntoResponse, $( + $X: Send + Sized + 'static, $X: Extract ),* { @@ -136,7 +175,7 @@ macro_rules! end_point_impl_raw { #[allow(unused_mut, non_snake_case)] fn call(&self, mut data: Data, mut req: Request, params: Option>, store: &Store) -> Self::Fut { let f = self.clone(); - $(let $X = $X::extract(&mut data, &mut req, ¶ms, store);)* + $(let $X = <$X as Extract>::extract(&mut data, &mut req, ¶ms, store);)* FutureObj::new(Box::new(async move { let (parts, _) = req.into_parts(); let head = Head::from(parts); @@ -160,6 +199,13 @@ macro_rules! end_point_impl { } } +macro_rules! seeded_end_point_impl { + ($(($X:ident,$Y:ident)),*) => { + seeded_end_point_impl_raw!([Head] $(($X,$Y)),*); + seeded_end_point_impl_raw!($(($X,$Y)),*); + } +} + end_point_impl!(); end_point_impl!(T0); end_point_impl!(T0, T1); @@ -171,3 +217,15 @@ end_point_impl!(T0, T1, T2, T3, T4, T5, T6); end_point_impl!(T0, T1, T2, T3, T4, T5, T6, T7); end_point_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8); end_point_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); + +seeded_end_point_impl!(); +seeded_end_point_impl!((T0,S0)); +seeded_end_point_impl!((T0,S0), (T1,S1)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4), (T5,S5)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4), (T5,S5), (T6,S6)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4), (T5,S5), (T6,S6), (T7,S7)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4), (T5,S5), (T6,S6), (T7,S7), (T8,S8)); +seeded_end_point_impl!((T0,S0), (T1,S1), (T2,S2), (T3,S3), (T4,S4), (T5,S5), (T6,S6), (T7,S7), (T8,S8), (T9,S9)); diff --git a/src/extract.rs b/src/extract.rs index d0988a451..ae5b55f8c 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -18,3 +18,35 @@ pub trait Extract: Send + Sized + 'static { store: &Store, ) -> Self::Fut; } + +/// A seed to extract `Param` for an app with `Data` +pub trait ExtractSeed: Send + Sync + Sized + 'static where Param: Send + Sized + 'static { + /// The async result of `extract`. + /// + /// The `Err` case represents that the endpoint should not be invoked, but + /// rather the given response should be returned immediately. + type Fut: Future> + Send + 'static; + + /// Attempt to extract a value from the given request. + fn extract(&self, + data: &mut Data, + req: &mut Request, + params: &Option>, + store: &Store, + ) -> Self::Fut; +} + +impl ExtractSeed for () + where Param: Extract + Send + Sized + 'static +{ + type Fut = Param::Fut; + + fn extract(&self, + data: &mut Data, + req: &mut Request, + params: &Option>, + store: &Store, + ) -> Self::Fut { + Param::extract(data, req, params, store) + } +} diff --git a/src/head.rs b/src/head.rs index b96e9b530..18881b1fa 100644 --- a/src/head.rs +++ b/src/head.rs @@ -4,10 +4,11 @@ //! automatically parse out information from a request. use futures::future; +use std::borrow::Cow; use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use crate::{configuration::Store, Extract, IntoResponse, Request, Response, RouteMatch}; +use crate::{configuration::Store, Extract, ExtractSeed, IntoResponse, Request, Response, RouteMatch}; /// Header and metadata for a request. /// @@ -52,6 +53,49 @@ impl Head { } } +pub struct NamedHeader(pub http::header::HeaderName); + +pub struct Header(pub T); + +impl From for NamedHeader { + fn from(name: http::header::HeaderName) -> Self { + NamedHeader(name) + } +} + +impl + Send + 'static, S: 'static> ExtractSeed, S> for NamedHeader { + type Fut = future::Ready, Response>>; + fn extract(&self, + data: &mut S, + req: &mut Request, + params: &Option>, + store: &Store, + ) -> Self::Fut { + let header = req.headers().get(&self.0); + match header { + Some(value) => future::ok(Header(value.clone().into())), + None => future::err(http::status::StatusCode::BAD_REQUEST.into_response()), + } + } +} + +impl + Send + 'static, S: 'static> ExtractSeed>, S> for NamedHeader { + type Fut = future::Ready>, Response>>; + fn extract(&self, + data: &mut S, + req: &mut Request, + params: &Option>, + store: &Store, + ) -> Self::Fut { + let header = req.headers().get(&self.0); + match header { + Some(value) => future::ok(Some(Header(value.clone().into()))), + None => future::ok(None), + } + } +} + + /// An extractor for path segments. /// /// Routes can use wildcard path segments (`{}`), which are then extracted by the endpoint using @@ -163,7 +207,7 @@ pub trait NamedSegment: Send + 'static + std::str::FromStr { /// } /// ``` /// -pub struct Named(pub T); +pub struct Named(pub T); impl Deref for Named { type Target = T; @@ -201,6 +245,34 @@ impl Extract for Named { } } +/// A seed extracting a particular segment. +/// +/// This extracts any `Named` where `T: std::str::FromStr` by looking up the particular segment. +pub struct SegmentName(pub Cow<'static, str>); + +impl ExtractSeed, S> for SegmentName { + type Fut = future::Ready, Response>>; + + fn extract(&self, + data: &mut S, + req: &mut Request, + params: &Option>, + store: &Store, + ) -> Self::Fut { + match params { + Some(params) => params + .map + .get(self.0.as_ref()) + .and_then(|segment| segment.parse().ok()) + .map_or( + future::err(http::status::StatusCode::BAD_REQUEST.into_response()), + |t| future::ok(Named(t)), + ), + None => future::err(http::status::StatusCode::BAD_REQUEST.into_response()), + } + } +} + /// An extractor for query string in URL /// pub struct UrlQuery(pub T); diff --git a/src/lib.rs b/src/lib.rs index baa99393d..94a72ce39 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,8 +29,8 @@ pub use crate::{ app::{App, AppData, Server}, configuration::ExtractConfiguration, cookies::Cookies, - endpoint::Endpoint, - extract::Extract, + endpoint::{Endpoint, Seeded}, + extract::{Extract, ExtractSeed}, middleware::Middleware, request::{Compute, Computed, Request}, response::{IntoResponse, Response},