From 99a6f8430f98e2cb3cf28fb8b88e2003829eba7a Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 20 Jan 2019 20:45:03 +0100 Subject: [PATCH 1/5] Relaxed Extract with uncoupled Self from Output --- src/endpoint.rs | 34 ++++++++++++++++++++++++++++++++-- src/extract.rs | 32 ++++++++++++++++++++++++++++++++ src/head.rs | 39 ++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- src/router.rs | 8 +++++++- 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/endpoint.rs b/src/endpoint.rs index 6b3c9295c..a4707532e 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,35 @@ macro_rules! call_f { }; } +pub struct Seeded(pub(crate) F, pub(crate) E); + +/// FIXME: implement this for all other parameter lengths. +impl Endpoint, Ty,)> for Seeded +where + F: Send + Sync + Clone + 'static + Fn(X) -> Fut, + Data: Send + Sync + Clone + 'static, + Fut: Future + Send + 'static, + Fut::Output: IntoResponse, + X: Send + Sized + 'static, + E: ExtractSeed, +{ + type Fut = FutureObj<'static, Response>; + fn call(&self, mut data: Data, mut req: Request, params: Option>, store: &Store) -> Self::Fut { + let f = self.0.clone(); + let x = (self.1).0.extract(&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!(f(x)); + res.into_response() + })) + } +} + macro_rules! end_point_impl_raw { ($([$head:ty])* $($X:ident),*) => { impl Endpoint, $($head,)* $(Ty<$X>),*)> for T @@ -128,6 +157,7 @@ macro_rules! end_point_impl_raw { Fut: Future + Send + 'static, Fut::Output: IntoResponse, $( + $X: Send + Sized + 'static, $X: Extract ),* { @@ -136,7 +166,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); 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..9b6150572 100644 --- a/src/head.rs +++ b/src/head.rs @@ -7,7 +7,7 @@ use futures::future; 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 +52,43 @@ impl Head { } } +pub struct NamedHeader(pub http::header::HeaderName); + +pub struct Header(pub T); + +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 diff --git a/src/lib.rs b/src/lib.rs index baa99393d..040557ab4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ pub use crate::{ configuration::ExtractConfiguration, cookies::Cookies, endpoint::Endpoint, - extract::Extract, + extract::{Extract, ExtractSeed}, middleware::Middleware, request::{Compute, Computed, Request}, response::{IntoResponse, Response}, diff --git a/src/router.rs b/src/router.rs index ef542203c..eb432ac2a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use crate::{ configuration::Store, - endpoint::{BoxedEndpoint, Endpoint}, + endpoint::{BoxedEndpoint, Endpoint, Seeded}, Middleware, }; use path_table::{PathTable, RouteMatch}; @@ -279,6 +279,12 @@ impl<'a, Data> Resource<'a, Data> { self.method(http::Method::GET, ep) } + pub fn get_with(&mut self, ep: T, seed: S) -> &mut EndpointData + where Seeded: Endpoint + { + self.method(http::Method::GET, Seeded(ep, seed)) + } + /// Add an endpoint for `HEAD` requests pub fn head, U>(&mut self, ep: T) -> &mut EndpointData { self.method(http::Method::HEAD, ep) From 6db9af375b18f4bc59d3e5f53b082c3b12461847 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 20 Jan 2019 21:52:40 +0100 Subject: [PATCH 2/5] Example exemplifying seeded extraction --- examples/seeded_extractor.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/seeded_extractor.rs diff --git a/examples/seeded_extractor.rs b/examples/seeded_extractor.rs new file mode 100644 index 000000000..7269e425a --- /dev/null +++ b/examples/seeded_extractor.rs @@ -0,0 +1,14 @@ +#![feature(async_await, futures_api)] + +use tide::head::{NamedHeader, Header}; +use http::header::{HeaderName, HeaderValue}; + +async fn display_header(value: Header) -> String { + String::from_utf8_lossy(value.0.as_ref()).into_owned() +} + +fn main() { + let mut app = tide::App::new(()); + app.at("/").get_with(display_header, (NamedHeader(HeaderName::from_static("user-agent")),)); + app.serve(); +} From 1099a87fa10686bc4e8ada2bd475c80fbe4a6a77 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 1 Mar 2019 15:37:39 +0100 Subject: [PATCH 3/5] Extraction of segments by runtime name --- src/head.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/head.rs b/src/head.rs index 9b6150572..18881b1fa 100644 --- a/src/head.rs +++ b/src/head.rs @@ -4,6 +4,7 @@ //! automatically parse out information from a request. use futures::future; +use std::borrow::Cow; use std::ops::{Deref, DerefMut}; use std::sync::Arc; @@ -56,6 +57,12 @@ 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, @@ -200,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; @@ -238,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); From cd48e8f367aff087e5b76c86038a5a2a4a1b0249 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 1 Mar 2019 15:40:01 +0100 Subject: [PATCH 4/5] Expand endpoint macros to up to 9 seeded arguments --- examples/seeded_extractor.rs | 9 +++- src/endpoint.rs | 82 ++++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/examples/seeded_extractor.rs b/examples/seeded_extractor.rs index 7269e425a..83f611de1 100644 --- a/examples/seeded_extractor.rs +++ b/examples/seeded_extractor.rs @@ -1,14 +1,19 @@ #![feature(async_await, futures_api)] -use tide::head::{NamedHeader, Header}; +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_with(display_header, (NamedHeader(HeaderName::from_static("user-agent")),)); + app.at("/").get_with(display_header, NamedHeader(HeaderName::from_static("user-agent"))); + app.at("/numbered/{num}").get_with(display_number, SegmentName("num".into())); app.serve(); } diff --git a/src/endpoint.rs b/src/endpoint.rs index a4707532e..6098037ab 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -119,33 +119,42 @@ macro_rules! call_f { }; } -pub struct Seeded(pub(crate) F, pub(crate) E); - -/// FIXME: implement this for all other parameter lengths. -impl Endpoint, Ty,)> for Seeded -where - F: Send + Sync + Clone + 'static + Fn(X) -> Fut, - Data: Send + Sync + Clone + 'static, - Fut: Future + Send + 'static, - Fut::Output: IntoResponse, - X: Send + Sized + 'static, - E: ExtractSeed, -{ - type Fut = FutureObj<'static, Response>; - fn call(&self, mut data: Data, mut req: Request, params: Option>, store: &Store) -> Self::Fut { - let f = self.0.clone(); - let x = (self.1).0.extract(&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!(f(x)); - res.into_response() - })) - } +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 { @@ -190,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); @@ -201,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)); From 3294cc68357b4531435e5aa545153999dc716dd7 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 1 Mar 2019 15:45:21 +0100 Subject: [PATCH 5/5] Uncouple endpoint construction from routing The result looks more clunky for binding it to a route but at least the concerns are separate more strictly. This also removes the need for duplicating the router utilities for methods. --- examples/seeded_extractor.rs | 5 +++-- src/lib.rs | 2 +- src/router.rs | 8 +------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/seeded_extractor.rs b/examples/seeded_extractor.rs index 83f611de1..71caf5bb3 100644 --- a/examples/seeded_extractor.rs +++ b/examples/seeded_extractor.rs @@ -1,5 +1,6 @@ #![feature(async_await, futures_api)] +use tide::Seeded; use tide::head::{NamedHeader, Header, SegmentName, Named}; use http::header::{HeaderName, HeaderValue}; @@ -13,7 +14,7 @@ async fn display_number(nr: Named) -> String { fn main() { let mut app = tide::App::new(()); - app.at("/").get_with(display_header, NamedHeader(HeaderName::from_static("user-agent"))); - app.at("/numbered/{num}").get_with(display_number, SegmentName("num".into())); + 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/lib.rs b/src/lib.rs index 040557ab4..94a72ce39 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use crate::{ app::{App, AppData, Server}, configuration::ExtractConfiguration, cookies::Cookies, - endpoint::Endpoint, + endpoint::{Endpoint, Seeded}, extract::{Extract, ExtractSeed}, middleware::Middleware, request::{Compute, Computed, Request}, diff --git a/src/router.rs b/src/router.rs index eb432ac2a..ef542203c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use crate::{ configuration::Store, - endpoint::{BoxedEndpoint, Endpoint, Seeded}, + endpoint::{BoxedEndpoint, Endpoint}, Middleware, }; use path_table::{PathTable, RouteMatch}; @@ -279,12 +279,6 @@ impl<'a, Data> Resource<'a, Data> { self.method(http::Method::GET, ep) } - pub fn get_with(&mut self, ep: T, seed: S) -> &mut EndpointData - where Seeded: Endpoint - { - self.method(http::Method::GET, Seeded(ep, seed)) - } - /// Add an endpoint for `HEAD` requests pub fn head, U>(&mut self, ep: T) -> &mut EndpointData { self.method(http::Method::HEAD, ep)