Skip to content

Commit

Permalink
merge upstream changes
Browse files Browse the repository at this point in the history
  • Loading branch information
tzilist committed Nov 11, 2018
2 parents caf0119 + 04321df commit f5995b0
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 100 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
Expand Up @@ -3,11 +3,10 @@ rust:
- nightly

before_script: |
rustup component add rustfmt-preview &&
cargo install clippy -f
rustup component add rustfmt-preview clippy-preview
script: |
cargo fmt -- --check &&
cargo clippy -- -D clippy &&
cargo clippy -- -D clippy::all &&
cargo build --verbose &&
cargo test --verbose
cache: cargo
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -26,3 +26,4 @@ features = ["compat"]
version = "0.3.0-alpha.9"

[dev-dependencies]
juniper = "0.10.0"
75 changes: 75 additions & 0 deletions examples/graphql.rs
@@ -0,0 +1,75 @@
// This example uses Juniper to process GraphQL requests. If you're not familiar with Juniper, take
// a look at [the Juniper book].
//
// [the Juniper book]: https://graphql-rust.github.io/

#![feature(async_await, futures_api)]

use http::status::StatusCode;
use juniper::graphql_object;
use std::sync::{atomic, Arc};
use tide::{body, App, AppData, Response};

// First, we define `Context` that holds accumulator state. This is accessible as App data in
// Tide, and as executor context in Juniper.
#[derive(Clone, Default)]
struct Context(Arc<atomic::AtomicIsize>);

impl juniper::Context for Context {}

// We define `Query` unit struct here. GraphQL queries will refer to this struct. The struct itself
// doesn't have any associated data (and there's no need to do so), but instead it exposes the
// accumulator state from the context.
struct Query;

graphql_object!(Query: Context |&self| {
// GraphQL integers are signed and 32 bits long.
field accumulator(&executor) -> i32 as "Current value of the accumulator" {
executor.context().0.load(atomic::Ordering::Relaxed) as i32
}
});

// Here is `Mutation` unit struct. GraphQL mutations will refer to this struct. This is similar to
// `Query`, but it provides the way to "mutate" the accumulator state.
struct Mutation;

graphql_object!(Mutation: Context |&self| {
field add(&executor, by: i32) -> i32 as "Add given value to the accumulator." {
executor.context().0.fetch_add(by as isize, atomic::Ordering::Relaxed) as i32 + by
}
});

// Adding `Query` and `Mutation` together we get `Schema`, which describes, well, the whole GraphQL
// schema.
type Schema = juniper::RootNode<'static, Query, Mutation>;

// Finally, we'll bridge between Tide and Juniper. `GraphQLRequest` from Juniper implements
// `Deserialize`, so we use `Json` extractor to deserialize the request body.
async fn handle_graphql(
ctx: AppData<Context>,
query: body::Json<juniper::http::GraphQLRequest>,
) -> Result<Response, StatusCode> {
let request = query.0;
let response = request.execute(&Schema::new(Query, Mutation), &ctx);

// `response` has the lifetime of `request`, so we can't use `IntoResponse` directly.
let body_vec = serde_json::to_vec(&response).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

http::Response::builder()
.status(if response.is_ok() {
StatusCode::OK
} else {
StatusCode::BAD_REQUEST
})
.header("Content-Type", "application/json")
.body(body::Body::from(body_vec))
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}

fn main() {
let mut app = App::new(Context::default());

app.at("/graphql").post(handle_graphql);

app.serve("127.0.0.1:7878");
}
2 changes: 1 addition & 1 deletion examples/named_path.rs
@@ -1,6 +1,6 @@
#![feature(async_await, futures_api)]

use tide::head::{NamedComponent, Named};
use tide::head::{Named, NamedComponent};

struct Number(i32);

Expand Down
2 changes: 1 addition & 1 deletion rustfmt.toml
@@ -1,2 +1,2 @@
edition = "2018"
tab_spaces = 4
tab_spaces = 4
76 changes: 41 additions & 35 deletions src/app.rs
Expand Up @@ -4,16 +4,16 @@ use futures::{
prelude::*,
};
use hyper::service::Service;
use std::{sync::Arc, ops::{Deref, DerefMut}};
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};

use crate::{
router::{Resource, Router},
Request,
extract::Extract,
RouteMatch,
Response,
body::Body,
Middleware,
extract::Extract,
router::{Resource, Router},
Middleware, Request, Response, RouteMatch,
};

/// The top-level type for setting up a Tide application.
Expand Down Expand Up @@ -65,13 +65,17 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
// TODO: be more robust
let addr = addr.to_socket_addrs().unwrap().next().unwrap();

let server = hyper::Server::bind(&addr).serve(move || {
let res: Result<_, std::io::Error> = Ok(server.clone());
res
}).compat().map(|_| {
let res: Result<(), ()> = Ok(());
res
}).compat();
let server = hyper::Server::bind(&addr)
.serve(move || {
let res: Result<_, std::io::Error> = Ok(server.clone());
res
})
.compat()
.map(|_| {
let res: Result<(), ()> = Ok(());
res
})
.compat();
hyper::rt::run(server);
}
}
Expand All @@ -98,26 +102,32 @@ impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {
let path = req.uri().path().to_owned();
let method = req.method().to_owned();

FutureObj::new(Box::new(async move {
if let Some((endpoint, params)) = router.route(&path, &method) {
for m in middleware.iter() {
match await!(m.request(&mut data, req, &params)) {
Ok(new_req) => req = new_req,
Err(resp) => return Ok(resp.map(Into::into)),
FutureObj::new(Box::new(
async move {
if let Some((endpoint, params)) = router.route(&path, &method) {
for m in middleware.iter() {
match await!(m.request(&mut data, req, &params)) {
Ok(new_req) => req = new_req,
Err(resp) => return Ok(resp.map(Into::into)),
}
}
}

let (head, mut resp) = await!(endpoint.call(data.clone(), req, params));
let (head, mut resp) = await!(endpoint.call(data.clone(), req, params));

for m in middleware.iter() {
resp = await!(m.response(&mut data, &head, resp))
}
for m in middleware.iter() {
resp = await!(m.response(&mut data, &head, resp))
}

Ok(resp.map(Into::into))
} else {
Ok(http::Response::builder().status(http::status::StatusCode::NOT_FOUND).body(hyper::Body::empty()).unwrap())
}
})).compat()
Ok(resp.map(Into::into))
} else {
Ok(http::Response::builder()
.status(http::status::StatusCode::NOT_FOUND)
.body(hyper::Body::empty())
.unwrap())
}
},
))
.compat()
}
}

Expand All @@ -140,11 +150,7 @@ impl<T> DerefMut for AppData<T> {

impl<T: Clone + Send + 'static> Extract<T> for AppData<T> {
type Fut = future::Ready<Result<Self, Response>>;
fn extract(
data: &mut T,
req: &mut Request,
params: &RouteMatch<'_>,
) -> Self::Fut {
fn extract(data: &mut T, req: &mut Request, params: &RouteMatch<'_>) -> Self::Fut {
future::ok(AppData(data.clone()))
}
}
10 changes: 5 additions & 5 deletions src/body.rs
Expand Up @@ -57,7 +57,7 @@ impl Body {
///
/// This method is asynchronous because, in general, it requires reading an async
/// stream of `BodyChunk` values.
pub async fn to_vec(&mut self) -> Result<Vec<u8>, Error> {
pub async fn read_to_vec(&mut self) -> Result<Vec<u8>, Error> {
match &mut self.inner {
BodyInner::Streaming(s) => {
let mut bytes = Vec::new();
Expand Down Expand Up @@ -131,7 +131,7 @@ impl<T: Send + serde::de::DeserializeOwned + 'static, S: 'static> Extract<S> for
let mut body = std::mem::replace(req.body_mut(), Body::empty());
FutureObj::new(Box::new(
async move {
let body = await!(body.to_vec()).map_err(mk_err)?;
let body = await!(body.read_to_vec()).map_err(mk_err)?;
let json: T = serde_json::from_slice(&body).map_err(mk_err)?;
Ok(Json(json))
},
Expand Down Expand Up @@ -160,7 +160,7 @@ impl<S: 'static> Extract<S> for Str {

FutureObj::new(Box::new(
async move {
let body = await!(body.to_vec().map_err(mk_err))?;
let body = await!(body.read_to_vec().map_err(mk_err))?;
let string = String::from_utf8(body).map_err(mk_err)?;
Ok(Str(string))
},
Expand All @@ -178,7 +178,7 @@ impl<S: 'static> Extract<S> for StrLossy {

FutureObj::new(Box::new(
async move {
let body = await!(body.to_vec().map_err(mk_err))?;
let body = await!(body.read_to_vec().map_err(mk_err))?;
let string = String::from_utf8_lossy(&body).to_string();
Ok(StrLossy(string))
},
Expand All @@ -196,7 +196,7 @@ impl<S: 'static> Extract<S> for Bytes {

FutureObj::new(Box::new(
async move {
let body = await!(body.to_vec().map_err(mk_err))?;
let body = await!(body.read_to_vec().map_err(mk_err))?;
Ok(Bytes(body))
},
))
Expand Down
59 changes: 17 additions & 42 deletions src/endpoint.rs
Expand Up @@ -14,10 +14,11 @@ pub trait Endpoint<Data, Kind>: Send + Sync + 'static {
fn call(&self, data: Data, req: Request, params: RouteMatch<'_>) -> Self::Fut;
}

type BoxedEndpointFn<Data> =
dyn Fn(Data, Request, RouteMatch) -> FutureObj<'static, (Head, Response)> + Send + Sync;

pub(crate) struct BoxedEndpoint<Data> {
endpoint: Box<
dyn Fn(Data, Request, RouteMatch) -> FutureObj<'static, (Head, Response)> + Send + Sync,
>,
endpoint: Box<BoxedEndpointFn<Data>>,
}

impl<Data> BoxedEndpoint<Data> {
Expand Down Expand Up @@ -46,46 +47,20 @@ impl<Data> BoxedEndpoint<Data> {
#[doc(hidden)]
pub struct Ty<T>(T);

macro_rules! end_point_impl_with_head {
($($X:ident),*) => {
impl<T, Data, Fut, $($X),*> Endpoint<Data, (Ty<Fut>, Head, $(Ty<$X>),*)> for T
where
T: Send + Sync + Clone + 'static + Fn(Head, $($X),*) -> Fut,
Data: Clone + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: IntoResponse,
$(
$X: Extract<Data>
),*
{
type Fut = FutureObj<'static, (Head, Response)>;

#[allow(unused_mut, non_snake_case)]
fn call(&self, mut data: Data, mut req: Request, params: RouteMatch<'_>) -> Self::Fut {
let f = self.clone();
$(let $X = $X::extract(&mut data, &mut req, &params);)*
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 (head, resp),
};)*
let res = await!(f(head.clone(), $($X),*));

(head, res.into_response())
}))
}
}
macro_rules! call_f {
($head_ty:ty; ($f:ident, $head:ident); $($X:ident),*) => {
$f($head.clone(), $($X),*)
};
(($f:ident, $head:ident); $($X:ident),*) => {
$f($($X),*)
};
}

// TODO: refactor to share code with the above macro
macro_rules! end_point_impl_no_head {
($($X:ident),*) => {
impl<T, Data, Fut, $($X),*> Endpoint<Data, (Ty<Fut>, $(Ty<$X>),*)> for T
macro_rules! end_point_impl_raw {
($([$head:ty])* $($X:ident),*) => {
impl<T, Data, Fut, $($X),*> Endpoint<Data, (Ty<Fut>, $($head,)* $(Ty<$X>),*)> for T
where
T: Send + Sync + Clone + 'static + Fn($($X),*) -> Fut,
T: Send + Sync + Clone + 'static + Fn($($head,)* $($X),*) -> Fut,
Data: Clone + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: IntoResponse,
Expand All @@ -106,7 +81,7 @@ macro_rules! end_point_impl_no_head {
Ok(x) => x,
Err(resp) => return (head, resp),
};)*
let res = await!(f($($X),*));
let res = await!(call_f!($($head;)* (f, head); $($X),*));

(head, res.into_response())
}))
Expand All @@ -117,8 +92,8 @@ macro_rules! end_point_impl_no_head {

macro_rules! end_point_impl {
($($X:ident),*) => {
end_point_impl_with_head!($($X),*);
end_point_impl_no_head!($($X),*);
end_point_impl_raw!([Head] $($X),*);
end_point_impl_raw!($($X),*);
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/head.rs
Expand Up @@ -75,8 +75,8 @@ pub trait NamedComponent: Send + 'static + std::str::FromStr {

/// An extractor for named path components
///
/// Allows routes to access named path components (`{foo}`). Each `Named<T>` extracts a single
/// component. `T` must implement the `NamedComponent` trait - to provide the component name - and the
/// Allows routes to access named path components (`{foo}`). Each `Named<T>` extracts a single
/// component. `T` must implement the `NamedComponent` trait - to provide the component name - and the
/// FromStr trait. Fails with a `BAD_REQUEST` response if the component is not found, fails to
/// parse or if multiple identically named components exist.
pub struct Named<T: NamedComponent>(pub T);
Expand All @@ -85,10 +85,13 @@ impl<T: NamedComponent, S: 'static> Extract<S> for Named<T> {
type Fut = future::Ready<Result<Self, Response>>;

fn extract(data: &mut S, req: &mut Request, params: &RouteMatch<'_>) -> Self::Fut {
params.map.get(T::NAME)
params
.map
.get(T::NAME)
.and_then(|component| component.parse().ok())
.map_or(future::err(http::status::StatusCode::BAD_REQUEST.into_response()),
|t| future::ok(Named(t)))
.map_or(
future::err(http::status::StatusCode::BAD_REQUEST.into_response()),
|t| future::ok(Named(t)),
)
}
}

0 comments on commit f5995b0

Please sign in to comment.