Improve Tide integration

This commit is contained in:
Koxiaet 2020-10-15 11:51:21 +01:00
parent a1d080dc15
commit f764edc7d1
5 changed files with 189 additions and 219 deletions

View File

@ -74,7 +74,7 @@ impl FromRequest for BatchRequest {
if req.method() == Method::GET { if req.method() == Method::GET {
let res = serde_urlencoded::from_str(req.query_string()); let res = serde_urlencoded::from_str(req.query_string());
Box::pin(async move { Ok(Self(res?)) }) Box::pin(async move { Ok(Self(async_graphql::BatchRequest::Single(res?))) })
} else { } else {
let content_type = req let content_type = req
.headers() .headers()

View File

@ -14,13 +14,11 @@ categories = ["network-programming", "asynchronous"]
[dependencies] [dependencies]
async-graphql = { path = "../..", version = "=2.0.3" } async-graphql = { path = "../..", version = "=2.0.3" }
tide = { version = "0.13.0", default-features = false, features = ["h1-server"] } tide = { version = "0.13.0", default-features = false, features = ["h1-server"] }
async-trait = "0.1.36"
serde_json = "1.0.56"
futures = "0.3.5"
async-std = "1.6.2"
pin-project-lite = "0.1.9"
[dev-dependencies] [dev-dependencies]
smol = { version = "0.1.18", features = ["tokio02"] } # Surf lacks multipart support
reqwest = "0.10.6" reqwest = { version = "0.10.8", default-features = false, features = ["json"] }
tokio = { version = "0.2.22", default-features = false, features = ["rt-threaded", "macros"] }
serde_json = "1.0.59"

View File

@ -1,5 +1,8 @@
//! Async-graphql integration with Tide //! Async-graphql integration with Tide
//! //!
//! Tide [does not support websockets](https://github.com/http-rs/tide/issues/67), so you can't use
//! subscriptions with it.
//!
//! # Examples //! # Examples
//! *[Full Example](<https://github.com/async-graphql/examples/blob/master/tide/starwars/src/main.rs>)* //! *[Full Example](<https://github.com/async-graphql/examples/blob/master/tide/starwars/src/main.rs>)*
@ -10,7 +13,7 @@
use async_graphql::http::MultipartOptions; use async_graphql::http::MultipartOptions;
use async_graphql::{ObjectType, ParseRequestError, Schema, SubscriptionType}; use async_graphql::{ObjectType, ParseRequestError, Schema, SubscriptionType};
use async_trait::async_trait; use tide::utils::async_trait;
use tide::{ use tide::{
http::{ http::{
headers::{self, HeaderValue}, headers::{self, HeaderValue},

View File

@ -1,19 +1,19 @@
mod test_utils; mod test_utils;
use serde_json::json;
use smol::{Task, Timer};
use std::io::Read; use std::io::Read;
use std::time::Duration;
use reqwest::{header, StatusCode};
use serde_json::json;
use async_graphql::*; use async_graphql::*;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[test] #[tokio::test(max_threads = 1)]
fn quickstart() -> Result<()> { async fn quickstart() -> Result<()> {
smol::run(async { let listen_addr = test_utils::find_listen_addr();
let listen_addr = test_utils::find_listen_addr().await;
let server = Task::<Result<()>>::spawn(async move { tokio::spawn(async move {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot { impl QueryRoot {
@ -28,64 +28,46 @@ fn quickstart() -> Result<()> {
let mut app = tide::new(); let mut app = tide::new();
let endpoint = async_graphql_tide::endpoint(schema); let endpoint = async_graphql_tide::endpoint(schema);
app.at("/").post(endpoint.clone()).get(endpoint); app.at("/").post(endpoint.clone()).get(endpoint);
app.listen(listen_addr).await?; app.listen(listen_addr).await
Ok(())
}); });
let client = Task::<Result<()>>::spawn(async move { test_utils::wait_server_ready().await;
Timer::after(Duration::from_millis(300)).await;
let resp = reqwest::Client::builder() let client = test_utils::client();
.no_proxy()
.build() let resp = client
.unwrap() .post(listen_addr)
.post(format!("http://{}", listen_addr).as_str()) .json(&json!({"query":"{ add(a: 10, b: 20) }"}))
.body(r#"{"query":"{ add(a: 10, b: 20) }"}"#)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.send() .send()
.await?; .await?;
assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let string = resp.text().await?; let string = resp.text().await?;
println!("via post {}", string); println!("via post {}", string);
assert_eq!(string, json!({"data": {"add": 30}}).to_string()); assert_eq!(string, json!({"data": {"add": 30}}).to_string());
// //
let resp = reqwest::Client::builder() let resp = client
.no_proxy() .get(listen_addr)
.build()
.unwrap()
.get(format!("http://{}", listen_addr).as_str())
.query(&[("query", "{ add(a: 10, b: 20) }")]) .query(&[("query", "{ add(a: 10, b: 20) }")])
.send() .send()
.await?; .await?;
assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let string = resp.text().await?; let string = resp.text().await?;
println!("via get {}", string); println!("via get {}", string);
assert_eq!(string, json!({"data": {"add": 30}}).to_string()); assert_eq!(string, json!({"data": {"add": 30}}).to_string());
Ok(()) Ok(())
});
client.await?;
server.cancel().await;
Ok(())
})
} }
#[test] #[tokio::test(max_threads = 1)]
fn hello() -> Result<()> { async fn hello() -> Result<()> {
smol::run(async { let listen_addr = test_utils::find_listen_addr();
let listen_addr = test_utils::find_listen_addr().await;
let server = Task::<Result<()>>::spawn(async move {
use tide::Request;
tokio::spawn(async move {
struct Hello(String); struct Hello(String);
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
@ -101,7 +83,7 @@ fn hello() -> Result<()> {
let mut app = tide::new(); let mut app = tide::new();
app.at("/").post(move |req: Request<()>| { app.at("/").post(move |req: tide::Request<()>| {
let schema = schema.clone(); let schema = schema.clone();
async move { async move {
let name = req let name = req
@ -115,42 +97,34 @@ fn hello() -> Result<()> {
async_graphql_tide::respond(schema.execute(req).await) async_graphql_tide::respond(schema.execute(req).await)
} }
}); });
app.listen(listen_addr).await?; app.listen(listen_addr).await
Ok(())
}); });
let client = Task::<Result<()>>::spawn(async move { test_utils::wait_server_ready().await;
Timer::after(Duration::from_millis(300)).await;
let resp = reqwest::Client::builder() let client = test_utils::client();
.no_proxy()
.build() let resp = client
.unwrap() .post(listen_addr)
.post(format!("http://{}", listen_addr).as_str()) .json(&json!({"query":"{ hello }"}))
.body(r#"{"query":"{ hello }"}"#)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header("Name", "Foo") .header("Name", "Foo")
.send() .send()
.await?; .await?;
assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let string = resp.text().await?; let string = resp.text().await?;
println!("{}", string); println!("{}", string);
assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string()); assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string());
let resp = reqwest::Client::builder() let resp = client
.no_proxy() .post(listen_addr)
.build() .json(&json!({"query":"{ hello }"}))
.unwrap() .header(header::CONTENT_TYPE, "application/json")
.post(format!("http://{}", listen_addr).as_str())
.body(r#"{"query":"{ hello }"}"#)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.send() .send()
.await?; .await?;
assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let string = resp.text().await?; let string = resp.text().await?;
println!("{}", string); println!("{}", string);
@ -160,21 +134,13 @@ fn hello() -> Result<()> {
); );
Ok(()) Ok(())
});
client.await?;
server.cancel().await;
Ok(())
})
} }
#[test] #[tokio::test(max_threads = 1)]
fn upload() -> Result<()> { async fn upload() -> Result<()> {
smol::run(async { let listen_addr = test_utils::find_listen_addr();
let listen_addr = test_utils::find_listen_addr().await;
let server = Task::<Result<()>>::spawn(async move { tokio::spawn(async move {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {}
@ -216,43 +182,29 @@ fn upload() -> Result<()> {
let mut app = tide::new(); let mut app = tide::new();
app.at("/").post(async_graphql_tide::endpoint(schema)); app.at("/").post(async_graphql_tide::endpoint(schema));
app.listen(listen_addr).await?; app.listen(listen_addr).await
Ok(())
}); });
let client = Task::<Result<()>>::spawn(async move { test_utils::wait_server_ready().await;
Timer::after(Duration::from_millis(300)).await;
let client = test_utils::client();
let form = reqwest::multipart::Form::new() let form = reqwest::multipart::Form::new()
.text("operations", r#"{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { filename, mimeType } }", "variables": { "file": null } }"#) .text("operations", r#"{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { filename, mimeType } }", "variables": { "file": null } }"#)
.text("map", r#"{ "0": ["variables.file"] }"#) .text("map", r#"{ "0": ["variables.file"] }"#)
.part("0", reqwest::multipart::Part::stream("test").file_name("test.txt").mime_str("text/plain")?); .part("0", reqwest::multipart::Part::stream("test").file_name("test.txt").mime_str("text/plain")?);
let resp = reqwest::Client::builder() let resp = client.post(listen_addr).multipart(form).send().await?;
.no_proxy()
.build()
.unwrap()
.post(format!("http://{}", listen_addr).as_str())
.multipart(form)
.send()
.await?;
assert_eq!(resp.status(), reqwest::StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let string = resp.text().await?; let string = resp.text().await?;
println!("{}", string); println!("{}", string);
assert_eq!( assert_eq!(
string, string,
json!({"data": {"singleUpload": {"filename": "test.txt", "mimeType": "text/plain"}}}).to_string() json!({"data": {"singleUpload": {"filename": "test.txt", "mimeType": "text/plain"}}})
.to_string()
); );
Ok(()) Ok(())
});
client.await?;
server.cancel().await;
Ok(())
})
} }

View File

@ -1,6 +1,23 @@
pub async fn find_listen_addr() -> std::net::SocketAddr { use reqwest::Client;
use std::time::Duration;
pub fn find_listen_addr() -> &'static str {
Box::leak(
format!(
"http://{}",
std::net::TcpListener::bind("localhost:0") std::net::TcpListener::bind("localhost:0")
.unwrap() .unwrap()
.local_addr() .local_addr()
.unwrap() .unwrap()
)
.into_boxed_str(),
)
}
pub fn client() -> Client {
Client::builder().no_proxy().build().unwrap()
}
pub async fn wait_server_ready() {
tokio::time::delay_for(Duration::from_millis(300)).await;
} }