Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend extractors with an optional seed per endpoint. #126

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/seeded_extractor.rs
Original file line number Diff line number Diff line change
@@ -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<HeaderValue>) -> String {
String::from_utf8_lossy(value.0.as_ref()).into_owned()
}

async fn display_number(nr: Named<i32>) -> 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();
}
62 changes: 60 additions & 2 deletions src/endpoint.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -119,6 +119,44 @@ macro_rules! call_f {
};
}

pub struct Seeded<F, E>(pub F, pub E);

macro_rules! seeded_end_point_impl_raw {
($([$head:ty])* $(($X:ident,$Y:ident)),*) => {
impl<T, Data, Fut, $($X,$Y),*> Endpoint<Data, (Ty<Fut>, $($head,)* $(Ty<$X>),*)> for Seeded<T, ($($Y),*)>
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<RouteMatch<'_>>, 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, &params, 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<T, Data, Fut, $($X),*> Endpoint<Data, (Ty<Fut>, $($head,)* $(Ty<$X>),*)> for T
Expand All @@ -128,6 +166,7 @@ macro_rules! end_point_impl_raw {
Fut: Future + Send + 'static,
Fut::Output: IntoResponse,
$(
$X: Send + Sized + 'static,
$X: Extract<Data>
),*
{
Expand All @@ -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<RouteMatch<'_>>, store: &Store) -> Self::Fut {
let f = self.clone();
$(let $X = $X::extract(&mut data, &mut req, &params, store);)*
$(let $X = <$X as Extract<Data>>::extract(&mut data, &mut req, &params, store);)*
FutureObj::new(Box::new(async move {
let (parts, _) = req.into_parts();
let head = Head::from(parts);
Expand All @@ -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);
Expand All @@ -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));
32 changes: 32 additions & 0 deletions src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,35 @@ pub trait Extract<Data>: Send + Sized + 'static {
store: &Store,
) -> Self::Fut;
}

/// A seed to extract `Param` for an app with `Data`
pub trait ExtractSeed<Param, Data>: 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<Output = Result<Param, Response>> + Send + 'static;

/// Attempt to extract a value from the given request.
fn extract(&self,
data: &mut Data,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
store: &Store,
) -> Self::Fut;
}

impl<Param, Data> ExtractSeed<Param, Data> for ()
where Param: Extract<Data> + Send + Sized + 'static
{
type Fut = Param::Fut;

fn extract(&self,
data: &mut Data,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
store: &Store,
) -> Self::Fut {
Param::extract(data, req, params, store)
}
}
76 changes: 74 additions & 2 deletions src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -52,6 +53,49 @@ impl Head {
}
}

pub struct NamedHeader(pub http::header::HeaderName);

pub struct Header<T>(pub T);

impl From<http::header::HeaderName> for NamedHeader {
fn from(name: http::header::HeaderName) -> Self {
NamedHeader(name)
}
}

impl<T: From<http::header::HeaderValue> + Send + 'static, S: 'static> ExtractSeed<Header<T>, S> for NamedHeader {
type Fut = future::Ready<Result<Header<T>, Response>>;
fn extract(&self,
data: &mut S,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
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<T: From<http::header::HeaderValue> + Send + 'static, S: 'static> ExtractSeed<Option<Header<T>>, S> for NamedHeader {
type Fut = future::Ready<Result<Option<Header<T>>, Response>>;
fn extract(&self,
data: &mut S,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
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
Expand Down Expand Up @@ -163,7 +207,7 @@ pub trait NamedSegment: Send + 'static + std::str::FromStr {
/// }
/// ```
///
pub struct Named<T: NamedSegment>(pub T);
pub struct Named<T>(pub T);

impl<T: NamedSegment> Deref for Named<T> {
type Target = T;
Expand Down Expand Up @@ -201,6 +245,34 @@ impl<T: NamedSegment, S: 'static> Extract<S> for Named<T> {
}
}

/// A seed extracting a particular segment.
///
/// This extracts any `Named<T>` where `T: std::str::FromStr` by looking up the particular segment.
pub struct SegmentName(pub Cow<'static, str>);

impl<T: std::str::FromStr + Send + 'static, S: 'static> ExtractSeed<Named<T>, S> for SegmentName {
type Fut = future::Ready<Result<Named<T>, Response>>;

fn extract(&self,
data: &mut S,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
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<T>(pub T);
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down