add async-graphql-tide

This commit is contained in:
vkill 2020-04-26 19:53:44 +08:00
parent 4850d7a77f
commit daa6ca7f20
5 changed files with 244 additions and 0 deletions

View File

@ -56,4 +56,5 @@ members = [
"async-graphql-derive",
"async-graphql-actix-web",
"async-graphql-warp",
"async-graphql-tide",
]

View File

@ -0,0 +1,22 @@
[package]
name = "async-graphql-tide"
version = "1.0.0"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
description = "async-graphql for tide"
publish = true
license = "MIT/Apache-2.0"
documentation = "https://docs.rs/async-graphql/"
homepage = "https://github.com/sunli829/async-graphql"
repository = "https://github.com/sunli829/async-graphql"
keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "..", version = "1.9.18" }
tide = "0.8"
[dev-dependencies]
async-std = "1.5.0"
surf = "2.0.0-alpha.1"
serde_json = "1.0.51"

View File

@ -0,0 +1,95 @@
//! Async-graphql integration with Tide
#![warn(missing_docs)]
#![allow(clippy::type_complexity)]
#![allow(clippy::needless_doctest_main)]
use async_graphql::http::{GQLRequest, GQLResponse};
use async_graphql::{
IntoQueryBuilder, IntoQueryBuilderOpts, ObjectType, QueryBuilder, Schema, SubscriptionType,
};
use tide::{Request, Response, StatusCode};
/// GraphQL request handler
///
/// It outputs a tuple containing the `Schema` and `QuertBuilder`.
///
/// # Examples
/// *[Full Example](<https://github.com/sunli829/async-graphql-examples/blob/master/tide/starwars/src/main.rs>)*
///
/// ```no_run
/// use async_graphql::*;
/// use async_std::task;
/// use tide::Request;
///
/// struct QueryRoot;
/// #[Object]
/// impl QueryRoot {
/// #[field(desc = "Returns the sum of a and b")]
/// async fn add(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }
/// }
///
/// fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// task::block_on(async {
/// let mut app = tide::new();
/// app.at("/").post(|req: Request<()>| async move {
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
/// async_graphql_tide::graphql(req, schema, |query_builder| query_builder).await
/// });
/// app.listen("0.0.0.0:8000").await?;
///
/// Ok(())
/// })
/// }
/// ```
pub async fn graphql<Query, Mutation, Subscription, TideState, F>(
req: Request<TideState>,
schema: Schema<Query, Mutation, Subscription>,
query_builder_configuration: F,
) -> tide::Result<Response>
where
Query: ObjectType + Send + Sync + 'static,
Mutation: ObjectType + Send + Sync + 'static,
Subscription: SubscriptionType + Send + Sync + 'static,
TideState: Send + Sync + 'static,
F: Fn(QueryBuilder) -> QueryBuilder,
{
graphql_opts(req, schema, query_builder_configuration, Default::default()).await
}
/// Similar to graphql, but you can set the options `IntoQueryBuilderOpts`.
pub async fn graphql_opts<Query, Mutation, Subscription, TideState, F>(
mut req: Request<TideState>,
schema: Schema<Query, Mutation, Subscription>,
query_builder_configuration: F,
opts: IntoQueryBuilderOpts,
) -> tide::Result<Response>
where
Query: ObjectType + Send + Sync + 'static,
Mutation: ObjectType + Send + Sync + 'static,
Subscription: SubscriptionType + Send + Sync + 'static,
TideState: Send + Sync + 'static,
F: Fn(QueryBuilder) -> QueryBuilder,
{
let gql_request: GQLRequest = req
.body_json()
.await
.map_err(|e| tide::Error::new(StatusCode::BadRequest, e))?;
let mut query_builder = gql_request
.into_query_builder_opts(&opts)
.await
.map_err(|e| tide::Error::new(StatusCode::BadRequest, e))?;
query_builder = query_builder_configuration(query_builder);
let query_response = query_builder.execute(&schema).await;
let gql_response = GQLResponse(query_response);
let resp = Response::new(StatusCode::Ok).body_json(&gql_response)?;
Ok(resp)
}

View File

@ -0,0 +1,119 @@
mod test_utils;
use async_std::prelude::*;
use async_std::task;
use serde_json::json;
use std::time::Duration;
use tide::Request;
use async_graphql::*;
#[test]
fn quickstart() -> tide::Result<()> {
task::block_on(async {
let port = test_utils::find_port().await;
let server = task::spawn(async move {
struct QueryRoot;
#[Object]
impl QueryRoot {
#[field(desc = "Returns the sum of a and b")]
async fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
}
let mut app = tide::new();
app.at("/").post(|req: Request<()>| async move {
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
async_graphql_tide::graphql(req, schema, |query_builder| query_builder).await
});
app.listen(&port).await?;
Ok(())
});
let client = task::spawn(async move {
task::sleep(Duration::from_millis(100)).await;
let string = surf::post(format!("http://{}", port))
.body_bytes(r#"{"query":"{ add(a: 10, b: 20) }"}"#)
.set_header("Content-Type".parse().unwrap(), "application/json")
.recv_string()
.await?;
assert_eq!(string, json!({"data": {"add": 30}}).to_string());
Ok(())
});
server.race(client).await
})
}
#[test]
fn hello() -> tide::Result<()> {
task::block_on(async {
let port = test_utils::find_port().await;
let server = task::spawn(async move {
struct Hello(String);
struct QueryRoot;
#[Object]
impl QueryRoot {
#[field(desc = "Returns hello")]
async fn hello<'a>(&self, ctx: &'a Context<'_>) -> String {
let name = ctx.data_opt::<Hello>().map(|hello| hello.0.as_str());
format!(
"Hello, {}!",
if let Some(name) = name { name } else { "world" }
)
}
}
struct ServerState {
schema: Schema<QueryRoot, EmptyMutation, EmptySubscription>,
}
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
let server_state = ServerState { schema };
let mut app = tide::with_state(server_state);
app.at("/").post(|req: Request<ServerState>| async move {
let schema = req.state().schema.clone();
let name = &req
.header(&"name".parse().unwrap())
.and_then(|values| values.first().map(|value| value.to_string()));
async_graphql_tide::graphql(req, schema, |mut query_builder| {
if let Some(name) = name {
query_builder = query_builder.data(Hello(name.to_string()))
}
query_builder
})
.await
});
app.listen(&port).await?;
Ok(())
});
let client = task::spawn(async move {
task::sleep(Duration::from_millis(100)).await;
let string = surf::post(format!("http://{}", port))
.body_bytes(r#"{"query":"{ hello }"}"#)
.set_header("Content-Type".parse().unwrap(), "application/json")
.set_header("Name".parse().unwrap(), "Foo")
.recv_string()
.await?;
assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string());
let string = surf::post(format!("http://{}", port))
.body_bytes(r#"{"query":"{ hello }"}"#)
.set_header("Content-Type".parse().unwrap(), "application/json")
.recv_string()
.await?;
assert_eq!(
string,
json!({"data":{"hello":"Hello, world!"}}).to_string()
);
Ok(())
});
server.race(client).await
})
}

View File

@ -0,0 +1,7 @@
pub async fn find_port() -> async_std::net::SocketAddr {
async_std::net::TcpListener::bind("localhost:0")
.await
.unwrap()
.local_addr()
.unwrap()
}