async-graphql-warp 2.0

This commit is contained in:
Sunli 2020-09-11 22:37:48 +08:00
parent c2e32c2ea1
commit a43e437a38
6 changed files with 52 additions and 105 deletions

View File

@ -60,6 +60,5 @@ members = [
"integrations/warp", "integrations/warp",
"integrations/tide", "integrations/tide",
"integrations/rocket", "integrations/rocket",
# "integrations/lambda",
"benchmark", "benchmark",
] ]

View File

@ -1,19 +0,0 @@
[package]
name = "async-graphql-lambda"
version = "1.18.0"
authors = ["Sunli <scott_s829@163.com>"]
edition = "2018"
description = "async-graphql for AWS Lambda"
publish = true
license = "MIT/Apache-2.0"
documentation = "https://docs.rs/async-graphql/"
homepage = "https://github.com/async-graphql/async-graphql"
repository = "https://github.com/async-graphql/async-graphql"
keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "1.18.0" }
lambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime" }
futures = "0.3.0"
async-trait = "0.1.30"

View File

@ -1,40 +0,0 @@
//! Async-graphql integration with AWS lambda
//!
#![forbid(unsafe_code)]
use async_graphql::{IntoQueryBuilder, IntoQueryBuilderOpts, ParseRequestError, QueryBuilder};
use futures::io::AllowStdIo;
use lambda_http::Request;
use std::io::Cursor;
/// Lambda request extension
///
#[async_trait::async_trait]
pub trait GQLRequestExt {
/// Convert a query to `async_graphql::QueryBuilder`.
async fn graphql(&self) -> Result<QueryBuilder, ParseRequestError> {
self.graphql_opts(Default::default()).await
}
/// Similar to graphql, but you can set the options `IntoQueryBuilderOpts`.
async fn graphql_opts(
&self,
opts: IntoQueryBuilderOpts,
) -> Result<QueryBuilder, ParseRequestError>;
}
#[async_trait::async_trait]
impl GQLRequestExt for Request {
async fn graphql_opts(
&self,
opts: IntoQueryBuilderOpts,
) -> Result<QueryBuilder, ParseRequestError> {
let ct = self
.headers()
.get("content-type")
.and_then(|value| value.to_str().ok());
(ct, AllowStdIo::new(Cursor::new(self.body().to_vec())))
.into_query_builder_opts(&opts)
.await
}
}

View File

@ -4,6 +4,7 @@ version = "1.18.0"
authors = ["Daniel Wiesenberg <daniel@simplificAR.io>"] authors = ["Daniel Wiesenberg <daniel@simplificAR.io>"]
edition = "2018" edition = "2018"
description = "async-graphql for Rocket.rs" description = "async-graphql for Rocket.rs"
# Waiting for Rocket 0.5 release.
#publish = true #publish = true
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
documentation = "https://docs.rs/async-graphql/" documentation = "https://docs.rs/async-graphql/"

View File

@ -62,7 +62,7 @@ where
graphql_opts(req, schema, configuration, Default::default()).await graphql_opts(req, schema, configuration, Default::default()).await
} }
/// Similar to graphql, but you can set the options `MultipartOptions`. /// Similar to graphql, but you can set the options `async_graphql::MultipartOptions`.
pub async fn graphql_opts<Query, Mutation, Subscription, TideState, F>( pub async fn graphql_opts<Query, Mutation, Subscription, TideState, F>(
req: Request<TideState>, req: Request<TideState>,
schema: Schema<Query, Mutation, Subscription>, schema: Schema<Query, Mutation, Subscription>,
@ -84,12 +84,12 @@ where
/// ///
#[async_trait] #[async_trait]
pub trait RequestExt<State: Clone + Send + Sync + 'static>: Sized { pub trait RequestExt<State: Clone + Send + Sync + 'static>: Sized {
/// Convert a query to `async_graphql::QueryBuilder`. /// Convert a query to `async_graphql::Request`.
async fn body_graphql(self) -> tide::Result<async_graphql::Request> { async fn body_graphql(self) -> tide::Result<async_graphql::Request> {
self.body_graphql_opts(Default::default()).await self.body_graphql_opts(Default::default()).await
} }
/// Similar to graphql, but you can set the options `IntoQueryBuilderOpts`. /// Similar to `RequestExt::body_graphql`, but you can set the options `async_graphql::MultipartOptions`.
async fn body_graphql_opts( async fn body_graphql_opts(
self, self,
opts: MultipartOptions, opts: MultipartOptions,

View File

@ -5,11 +5,8 @@
#![allow(clippy::needless_doctest_main)] #![allow(clippy::needless_doctest_main)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use async_graphql::http::{GQLRequest, StreamBody}; use async_graphql::http::{GQLRequest, MultipartOptions, StreamBody};
use async_graphql::{ use async_graphql::{Data, FieldResult, ObjectType, Schema, SubscriptionType};
Data, FieldResult, IntoQueryBuilder, ObjectType, QueryBuilder, ReceiveMultipartOptions,
Response, Schema, SubscriptionType, WebSocketTransport,
};
use futures::select; use futures::select;
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use hyper::Method; use hyper::Method;
@ -35,9 +32,10 @@ impl Reject for BadRequest {}
/// GraphQL request filter /// GraphQL request filter
/// ///
/// It outputs a tuple containing the `Schema` and `QuertBuilder`. /// It outputs a tuple containing the `async_graphql::Schema` and `async_graphql::Request`.
/// ///
/// # Examples /// # Examples
///
/// *[Full Example](<https://github.com/async-graphql/examples/blob/master/warp/starwars/src/main.rs>)* /// *[Full Example](<https://github.com/async-graphql/examples/blob/master/warp/starwars/src/main.rs>)*
/// ///
/// ```no_run /// ```no_run
@ -57,18 +55,24 @@ impl Reject for BadRequest {}
/// } /// }
/// } /// }
/// ///
/// type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
///
/// #[tokio::main] /// #[tokio::main]
/// async fn main() { /// async fn main() {
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
/// let filter = async_graphql_warp::graphql(schema).and_then(|(schema, builder): (_, QueryBuilder)| async move { /// let filter = async_graphql_warp::graphql(schema).
/// Ok::<_, Infallible>(GQLResponse::from(builder.execute(&schema).await)) /// and_then(|(schema, request): (MySchema, async_graphql::Request)| async move {
/// Ok::<_, Infallible>(GQLResponse::from(schema.execute(request).await))
/// }); /// });
/// warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; /// warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
/// } /// }
/// ``` /// ```
pub fn graphql<Query, Mutation, Subscription>( pub fn graphql<Query, Mutation, Subscription>(
schema: Schema<Query, Mutation, Subscription>, schema: Schema<Query, Mutation, Subscription>,
) -> BoxedFilter<((Schema<Query, Mutation, Subscription>, QueryBuilder),)> ) -> BoxedFilter<((
Schema<Query, Mutation, Subscription>,
async_graphql::Request,
),)>
where where
Query: ObjectType + Send + Sync + 'static, Query: ObjectType + Send + Sync + 'static,
Mutation: ObjectType + Send + Sync + 'static, Mutation: ObjectType + Send + Sync + 'static,
@ -77,11 +81,14 @@ where
graphql_opts(schema, Default::default()) graphql_opts(schema, Default::default())
} }
/// Similar to graphql, but you can set the options `IntoQueryBuilderOpts`. /// Similar to graphql, but you can set the options `async_graphql::MultipartOptions`.
pub fn graphql_opts<Query, Mutation, Subscription>( pub fn graphql_opts<Query, Mutation, Subscription>(
schema: Schema<Query, Mutation, Subscription>, schema: Schema<Query, Mutation, Subscription>,
opts: ReceiveMultipartOptions, opts: MultipartOptions,
) -> BoxedFilter<((Schema<Query, Mutation, Subscription>, QueryBuilder),)> ) -> BoxedFilter<((
Schema<Query, Mutation, Subscription>,
async_graphql::Request,
),)>
where where
Query: ObjectType + Send + Sync + 'static, Query: ObjectType + Send + Sync + 'static,
Mutation: ObjectType + Send + Sync + 'static, Mutation: ObjectType + Send + Sync + 'static,
@ -100,22 +107,19 @@ where
query: String, query: String,
content_type, content_type,
body, body,
opts: Arc<ReceiveMultipartOptions>, opts: Arc<MultipartOptions>,
schema| async move { schema| async move {
if method == Method::GET { if method == Method::GET {
let gql_request: GQLRequest = serde_urlencoded::from_str(&query) let request: GQLRequest = serde_urlencoded::from_str(&query)
.map_err(|err| warp::reject::custom(BadRequest(err.into())))?; .map_err(|err| warp::reject::custom(BadRequest(err.into())))?;
let builder = gql_request Ok::<_, Rejection>((schema, async_graphql::Request::from(request)))
.into_query_builder_opts(&opts)
.await
.map_err(|err| warp::reject::custom(BadRequest(err.into())))?;
Ok::<_, Rejection>((schema, builder))
} else { } else {
let builder = (content_type, StreamBody::new(body)) let request = async_graphql::http::receive_body(
.into_query_builder_opts(&opts) content_type,
.await StreamBody::new(body),
.map_err(|err| warp::reject::custom(BadRequest(err.into())))?; MultipartOptions::clone(&opts)
Ok::<_, Rejection>((schema, builder)) ).await.map_err(|err| warp::reject::custom(BadRequest(err.into())))?;
Ok::<_, Rejection>((schema, request))
} }
}, },
) )
@ -163,15 +167,15 @@ where
Mutation: ObjectType + Sync + Send + 'static, Mutation: ObjectType + Sync + Send + 'static,
Subscription: SubscriptionType + Send + Sync + 'static, Subscription: SubscriptionType + Send + Sync + 'static,
{ {
graphql_subscription_with_data(schema, |_| Ok(Default::default())) graphql_subscription_with_initializer(schema, |_| Ok(Default::default()))
} }
/// GraphQL subscription filter /// GraphQL subscription filter
/// ///
/// Specifies that a function converts the init payload to data. /// Specifies that a function converts the init payload to data.
pub fn graphql_subscription_with_data<Query, Mutation, Subscription, F>( pub fn graphql_subscription_with_initializer<Query, Mutation, Subscription, F>(
schema: Schema<Query, Mutation, Subscription>, schema: Schema<Query, Mutation, Subscription>,
init_context_data: F, initializer: F,
) -> BoxedFilter<(impl Reply,)> ) -> BoxedFilter<(impl Reply,)>
where where
Query: ObjectType + Sync + Send + 'static, Query: ObjectType + Sync + Send + 'static,
@ -182,20 +186,23 @@ where
warp::any() warp::any()
.and(warp::ws()) .and(warp::ws())
.and(warp::any().map(move || schema.clone())) .and(warp::any().map(move || schema.clone()))
.and(warp::any().map(move || init_context_data.clone())) .and(warp::any().map(move || initializer.clone()))
.map( .map(
|ws: warp::ws::Ws, |ws: warp::ws::Ws, schema: Schema<Query, Mutation, Subscription>, initializer: F| {
schema: Schema<Query, Mutation, Subscription>,
init_context_data: F| {
ws.on_upgrade(move |websocket| { ws.on_upgrade(move |websocket| {
let (mut tx, rx) = websocket.split(); let (mut tx, rx) = websocket.split();
let (mut stx, srx) = let (mut stx, srx) =
schema.subscription_connection(WebSocketTransport::new(init_context_data)); async_graphql::transports::websocket::create_with_initializer(
&schema,
initializer,
);
let mut rx = rx.fuse(); let mut rx = rx.fuse();
let mut srx = srx.fuse(); let srx = srx.fuse();
async move { async move {
futures::pin_mut!(srx);
loop { loop {
select! { select! {
bytes = srx.next() => { bytes = srx.next() => {
@ -231,17 +238,17 @@ where
} }
/// GraphQL reply /// GraphQL reply
pub struct GQLResponse(async_graphql::Result<Response>); pub struct GQLResponse(async_graphql::Response);
impl From<async_graphql::Result<Response>> for GQLResponse { impl From<async_graphql::Response> for GQLResponse {
fn from(resp: async_graphql::Result<Response>) -> Self { fn from(resp: async_graphql::Response) -> Self {
GQLResponse(resp) GQLResponse(resp)
} }
} }
fn add_cache_control(http_resp: &mut Response, resp: &async_graphql::Result<Response>) { fn add_cache_control(http_resp: &mut Response, resp: &async_graphql::Response) {
if let Ok(Response { cache_control, .. }) = resp { if resp.is_ok() {
if let Some(cache_control) = cache_control.value() { if let Some(cache_control) = resp.cache_control.value() {
if let Ok(value) = cache_control.parse() { if let Ok(value) = cache_control.parse() {
http_resp.headers_mut().insert("cache-control", value); http_resp.headers_mut().insert("cache-control", value);
} }
@ -251,14 +258,13 @@ fn add_cache_control(http_resp: &mut Response, resp: &async_graphql::Result<Resp
impl Reply for GQLResponse { impl Reply for GQLResponse {
fn into_response(self) -> Response { fn into_response(self) -> Response {
let gql_resp = async_graphql::http::GQLResponse(self.0);
let mut resp = warp::reply::with_header( let mut resp = warp::reply::with_header(
warp::reply::json(&gql_resp), warp::reply::json(&self.0),
"content-type", "content-type",
"application/json", "application/json",
) )
.into_response(); .into_response();
add_cache_control(&mut resp, &gql_resp.0); add_cache_control(&mut resp, &self.0);
resp resp
} }
} }