From dcbb74155d356bc6a0ee644eff61053f9af5ef4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 8 Sep 2019 13:19:37 +0900 Subject: [PATCH 01/25] juniper_codegen: Drop regex & lazy_static dependencies (#424) * Replace regex & lazy_static with a simple check. --- juniper_codegen/Cargo.toml | 2 -- juniper_codegen/src/util.rs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index dcadf4ef..fecf37cf 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -18,8 +18,6 @@ proc-macro = true proc-macro2 = "1.0.1" syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] } quote = "1.0.2" -regex = "1" -lazy_static = "1.0.0" [dev-dependencies] juniper = { version = "0.13.1", path = "../juniper" } diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index b3ca4845..d11f8788 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -1,5 +1,4 @@ use quote::quote; -use regex::Regex; use std::collections::HashMap; use syn::{ parse, parse_quote, punctuated::Punctuated, Attribute, Lit, Meta, MetaList, MetaNameValue, @@ -279,10 +278,16 @@ pub(crate) fn to_upper_snake_case(s: &str) -> String { #[doc(hidden)] pub fn is_valid_name(field_name: &str) -> bool { - lazy_static::lazy_static! { - static ref GRAPHQL_NAME_SPEC: Regex = Regex::new("^[_A-Za-z][_0-9A-Za-z]*$").unwrap(); - } - GRAPHQL_NAME_SPEC.is_match(field_name) + let mut chars = field_name.chars(); + + match chars.next() { + // first char can't be a digit + Some(c) if c.is_ascii_alphabetic() || c == '_' => (), + // can't be an empty string or any other character + _ => return false, + }; + + chars.all(|c| c.is_ascii_alphanumeric() || c == '_') } #[derive(Default, Debug)] From b61aa900b10c964fc5f94bd4e7925fb15cab827c Mon Sep 17 00:00:00 2001 From: nWacky <38620459+nWacky@users.noreply.github.com> Date: Mon, 30 Sep 2019 03:43:56 +0300 Subject: [PATCH 02/25] Add subscription support to query parser (#430) --- juniper/CHANGELOG.md | 2 +- juniper/src/ast.rs | 1 + juniper/src/executor/mod.rs | 6 ++++++ juniper/src/integrations/serde.rs | 4 ++++ juniper/src/lib.rs | 1 + juniper/src/parser/document.rs | 11 ++++++++--- juniper/src/schema/model.rs | 14 ++++++++++++++ juniper/src/schema/schema.rs | 3 +-- juniper/src/tests/schema_introspection.rs | 11 +++++++++++ juniper/src/validation/rules/known_directives.rs | 7 ++++++- juniper/src/validation/visitor.rs | 11 +++++++++++ 11 files changed, 64 insertions(+), 7 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 75210006..ad248a21 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,6 +1,6 @@ # master -- No changes yet +- Add ability to parse 'subscription' # [[0.13.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.13.1) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index e27f733e..0a99c724 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -116,6 +116,7 @@ pub struct Directive<'a, S> { pub enum OperationType { Query, Mutation, + Subscription, } #[derive(Clone, PartialEq, Debug)] diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index a0dc0e11..d7489393 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -648,6 +648,10 @@ where None => return Err(GraphQLError::UnknownOperationName), }; + if op.item.operation_type == OperationType::Subscription { + return Err(GraphQLError::IsSubscription); + } + let default_variable_values = op.item.variable_definitions.map(|defs| { defs.item .items @@ -683,6 +687,7 @@ where .schema .mutation_type() .expect("No mutation type found"), + OperationType::Subscription => unreachable!(), }; let executor = Executor { @@ -705,6 +710,7 @@ where OperationType::Mutation => { executor.resolve_into_value(&root_node.mutation_info, &root_node.mutation_type) } + OperationType::Subscription => unreachable!(), }; } diff --git a/juniper/src/integrations/serde.rs b/juniper/src/integrations/serde.rs index 558ce108..c91c5262 100644 --- a/juniper/src/integrations/serde.rs +++ b/juniper/src/integrations/serde.rs @@ -70,6 +70,10 @@ impl<'a> ser::Serialize for GraphQLError<'a> { message: "Unknown operation", }] .serialize(serializer), + GraphQLError::IsSubscription => [SerializeHelper { + message: "Expected query, got subscription", + }] + .serialize(serializer), } } } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 0a71db82..2b010a6a 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -185,6 +185,7 @@ pub enum GraphQLError<'a> { NoOperationProvided, MultipleOperationsProvided, UnknownOperationName, + IsSubscription, } /// Execute a query in a provided schema diff --git a/juniper/src/parser/document.rs b/juniper/src/parser/document.rs index 091fdea1..4e851a91 100644 --- a/juniper/src/parser/document.rs +++ b/juniper/src/parser/document.rs @@ -56,9 +56,12 @@ where S: ScalarValue, { match parser.peek().item { - Token::CurlyOpen | Token::Name("query") | Token::Name("mutation") => Ok( - Definition::Operation(parse_operation_definition(parser, schema)?), - ), + Token::CurlyOpen + | Token::Name("query") + | Token::Name("mutation") + | Token::Name("subscription") => Ok(Definition::Operation(parse_operation_definition( + parser, schema, + )?)), Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition( parser, schema, )?)), @@ -95,6 +98,7 @@ where let op = match operation_type.item { OperationType::Query => Some(schema.concrete_query_type()), OperationType::Mutation => schema.concrete_mutation_type(), + OperationType::Subscription => schema.concrete_subscription_type(), }; let fields = op.and_then(|m| m.fields(schema)); let fields = fields.as_ref().map(|c| c as &[_]); @@ -394,6 +398,7 @@ fn parse_operation_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Operatio match parser.peek().item { Token::Name("query") => Ok(parser.next()?.map(|_| OperationType::Query)), Token::Name("mutation") => Ok(parser.next()?.map(|_| OperationType::Mutation)), + Token::Name("subscription") => Ok(parser.next()?.map(|_| OperationType::Subscription)), _ => Err(parser.next()?.map(ParseError::UnexpectedToken)), } } diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index f3007b5b..a1c003d7 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -65,6 +65,7 @@ pub struct DirectiveType<'a, S> { pub enum DirectiveLocation { Query, Mutation, + Subscription, Field, #[graphql(name = "FRAGMENT_DEFINITION")] FragmentDefinition, @@ -243,6 +244,18 @@ impl<'a, S> SchemaType<'a, S> { }) } + pub fn subscription_type(&self) -> Option> { + // subscription is not yet in `RootNode`, + // so return `None` for now + None + } + + pub fn concrete_subscription_type(&self) -> Option<&MetaType> { + // subscription is not yet in `RootNode`, + // so return `None` for now + None + } + pub fn type_list(&self) -> Vec> { self.types.values().map(|t| TypeType::Concrete(t)).collect() } @@ -452,6 +465,7 @@ impl fmt::Display for DirectiveLocation { f.write_str(match *self { DirectiveLocation::Query => "query", DirectiveLocation::Mutation => "mutation", + DirectiveLocation::Subscription => "subscription", DirectiveLocation::Field => "field", DirectiveLocation::FragmentDefinition => "fragment definition", DirectiveLocation::FragmentSpread => "fragment spread", diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 79a72125..a273fa7e 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -104,9 +104,8 @@ where self.mutation_type() } - // Included for compatibility with the introspection query in GraphQL.js fn subscription_type(&self) -> Option> { - None + self.subscription_type() } fn directives(&self) -> Vec<&DirectiveType> { diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 86b4dd13..4303c676 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -997,6 +997,12 @@ pub(crate) fn schema_introspection_result() -> value::Value { "isDeprecated": false, "deprecationReason": Null }, + { + "name": "SUBSCRIPTION", + "description": Null, + "isDeprecated": false, + "deprecationReason": Null + }, { "name": "FIELD", "description": Null, @@ -2204,6 +2210,11 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> value::Value "isDeprecated": false, "deprecationReason": Null }, + { + "name": "SUBSCRIPTION", + "isDeprecated": false, + "deprecationReason": Null + }, { "name": "FIELD", "isDeprecated": false, diff --git a/juniper/src/validation/rules/known_directives.rs b/juniper/src/validation/rules/known_directives.rs index 541c0daf..902bb48b 100644 --- a/juniper/src/validation/rules/known_directives.rs +++ b/juniper/src/validation/rules/known_directives.rs @@ -28,6 +28,7 @@ where self.location_stack.push(match op.item.operation_type { OperationType::Query => DirectiveLocation::Query, OperationType::Mutation => DirectiveLocation::Mutation, + OperationType::Subscription => DirectiveLocation::Subscription, }); } @@ -37,7 +38,11 @@ where _: &'a Spanning>, ) { let top = self.location_stack.pop(); - assert!(top == Some(DirectiveLocation::Query) || top == Some(DirectiveLocation::Mutation)); + assert!( + top == Some(DirectiveLocation::Query) + || top == Some(DirectiveLocation::Mutation) + || top == Some(DirectiveLocation::Subscription) + ); } fn enter_field(&mut self, _: &mut ValidatorContext<'a, S>, _: &'a Spanning>) { diff --git a/juniper/src/validation/visitor.rs b/juniper/src/validation/visitor.rs index 05aa4037..255cee43 100644 --- a/juniper/src/validation/visitor.rs +++ b/juniper/src/validation/visitor.rs @@ -64,6 +64,17 @@ fn visit_definitions<'a, S, V>( .schema .concrete_mutation_type() .map(|t| Type::NonNullNamed(Cow::Borrowed(t.name().unwrap()))), + Definition::Operation(Spanning { + item: + Operation { + operation_type: OperationType::Subscription, + .. + }, + .. + }) => ctx + .schema + .concrete_subscription_type() + .map(|t| Type::NonNullNamed(Cow::Borrowed(t.name().unwrap()))), }; ctx.with_pushed_type(def_type.as_ref(), |ctx| { From 5be66654a961241839bd9f1296f916d1dd08aaa8 Mon Sep 17 00:00:00 2001 From: James Harton <59449+jimsynz@users.noreply.github.com> Date: Mon, 30 Sep 2019 14:00:45 +1300 Subject: [PATCH 03/25] Improve visitability of lookahead types. (#431) I've added methods which allow Juniper users to visit all nodes of a lookahead tree so that they can be used for query generation. --- juniper/CHANGELOG.md | 11 ++-- juniper/src/executor/look_ahead.rs | 99 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index ad248a21..31f9c44a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,10 +1,11 @@ # master -- Add ability to parse 'subscription' +- Improve lookahead visitability. +- Add ability to parse 'subscription'. # [[0.13.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.13.1) -- Fix a regression when using lookaheads with fragments containing nested types [#404](https://github.com/graphql-rust/juniper/pull/404) +- Fix a regression when using lookaheads with fragments containing nested types [#404](https://github.com/graphql-rust/juniper/pull/404) - Allow `mut` arguments for resolver functions in `#[object]` macros [#402](https://github.com/graphql-rust/juniper/pull/402) @@ -15,7 +16,7 @@ See [#345](https://github.com/graphql-rust/juniper/pull/345). The newtype pattern can now be used with the `GraphQLScalarValue` custom derive -to easily implement custom scalar values that just wrap another scalar, +to easily implement custom scalar values that just wrap another scalar, similar to serdes `#[serde(transparent)]` functionality. Example: @@ -34,7 +35,7 @@ struct UserId(i32); ### object macro -The `graphql_object!` macro is deprecated and will be removed in the future. +The `graphql_object!` macro is deprecated and will be removed in the future. It is replaced by the new [object](https://docs.rs/juniper/latest/juniper/macro.object.html) procedural macro. [#333](https://github.com/graphql-rust/juniper/pull/333) @@ -53,7 +54,7 @@ This should not have any impact on your code, since juniper already was 2018 com - The `GraphQLType` impl for () was removed to improve compile time safefty. [#355](https://github.com/graphql-rust/juniper/pull/355) - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - Added built-in support for the canonical schema introspection query via - `juniper::introspect()`. + `juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307) - Fix introspection query validity The DirectiveLocation::InlineFragment had an invalid literal value, diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index e4e4fe4f..3c8fd7e4 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -88,6 +88,11 @@ where } } + /// The argument's name + pub fn name(&'a self) -> &str { + &self.name + } + /// The value of the argument pub fn value(&'a self) -> &LookAheadValue<'a, S> { &self.value @@ -347,6 +352,12 @@ pub trait LookAheadMethods { self.select_child(name).is_some() } + /// Does the current node have any arguments? + fn has_arguments(&self) -> bool; + + /// Does the current node have any children? + fn has_children(&self) -> bool; + /// Get the top level arguments for the current selection fn arguments(&self) -> &[LookAheadArgument]; @@ -354,6 +365,9 @@ pub trait LookAheadMethods { fn argument(&self, name: &str) -> Option<&LookAheadArgument> { self.arguments().iter().find(|a| a.name == name) } + + /// Get the top level children for the current selection + fn child_names(&self) -> Vec<&str>; } impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { @@ -368,6 +382,21 @@ impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { fn arguments(&self) -> &[LookAheadArgument] { &self.arguments } + + fn child_names(&self) -> Vec<&str> { + self.children + .iter() + .map(|c| c.alias.unwrap_or(c.name)) + .collect() + } + + fn has_arguments(&self) -> bool { + !self.arguments.is_empty() + } + + fn has_children(&self) -> bool { + !self.children.is_empty() + } } impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { @@ -385,6 +414,21 @@ impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { fn arguments(&self) -> &[LookAheadArgument] { &self.arguments } + + fn child_names(&self) -> Vec<&str> { + self.children + .iter() + .map(|c| c.inner.alias.unwrap_or(c.inner.name)) + .collect() + } + + fn has_arguments(&self) -> bool { + !self.arguments.is_empty() + } + + fn has_children(&self) -> bool { + !self.children.is_empty() + } } #[cfg(test)] @@ -1399,4 +1443,59 @@ fragment heroFriendNames on Hero { panic!("No Operation found"); } } + + #[test] + fn check_visitability() { + let docs = parse_document_source::( + " +query Hero { + hero(episode: EMPIRE) { + name + friends { + name + } + } +} + ", + ) + .unwrap(); + let fragments = extract_fragments(&docs); + + if let crate::ast::Definition::Operation(ref op) = docs[0] { + let vars = Variables::default(); + let look_ahead = LookAheadSelection::build_from_selection( + &op.item.selection_set[0], + &vars, + &fragments, + ) + .unwrap(); + + assert_eq!(look_ahead.field_name(), "hero"); + + assert!(look_ahead.has_arguments()); + let args = look_ahead.arguments(); + assert_eq!(args[0].name(), "episode"); + assert_eq!(args[0].value(), &LookAheadValue::Enum("EMPIRE")); + + assert!(look_ahead.has_children()); + assert_eq!(look_ahead.child_names(), vec!["name", "friends"]); + + let child0 = look_ahead.select_child("name").unwrap(); + assert_eq!(child0.field_name(), "name"); + assert!(!child0.has_arguments()); + assert!(!child0.has_children()); + + let child1 = look_ahead.select_child("friends").unwrap(); + assert_eq!(child1.field_name(), "friends"); + assert!(!child1.has_arguments()); + assert!(child1.has_children()); + assert_eq!(child1.child_names(), vec!["name"]); + + let child2 = child1.select_child("name").unwrap(); + assert!(!child2.has_arguments()); + assert!(!child2.has_children()); + } else { + panic!("No Operation found"); + } + } } From 02fd164e96d36e48e4dbfea7a360ef6fd4337d34 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 18:47:37 -0700 Subject: [PATCH 04/25] Update `url` dependency (#432) --- juniper/CHANGELOG.md | 1 + juniper/Cargo.toml | 2 +- juniper_hyper/Cargo.toml | 2 +- juniper_hyper/examples/hyper_server.rs | 2 +- juniper_iron/Cargo.toml | 3 ++- juniper_iron/src/lib.rs | 9 ++++++--- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 31f9c44a..c24b1c0b 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,5 +1,6 @@ # master +- Require `url` 2.x if `url` feature is enabled. - Improve lookahead visitability. - Add ability to parse 'subscription'. diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index a72c7d7a..a581977e 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -41,7 +41,7 @@ serde_derive = { version = "1.0.2" } chrono = { version = "0.4.0", optional = true } serde_json = { version="1.0.2", optional = true } -url = { version = "1.5.1", optional = true } +url = { version = "2", optional = true } uuid = { version = "0.7", optional = true } [dev-dependencies] diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index eecc445a..4017ee51 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" -url = "1.7" +url = "2" juniper = { version = "0.13.1" , default-features = false, path = "../juniper"} futures = "0.1" diff --git a/juniper_hyper/examples/hyper_server.rs b/juniper_hyper/examples/hyper_server.rs index 208f8e1b..802131e5 100644 --- a/juniper_hyper/examples/hyper_server.rs +++ b/juniper_hyper/examples/hyper_server.rs @@ -27,7 +27,7 @@ fn main() { let new_service = move || { let root_node = root_node.clone(); let ctx = db.clone(); - service_fn(move |req| -> Box + Send> { + service_fn(move |req| -> Box + Send> { let root_node = root_node.clone(); let ctx = ctx.clone(); match (req.method(), req.uri().path()) { diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml index 510537f7..f6ac6661 100644 --- a/juniper_iron/Cargo.toml +++ b/juniper_iron/Cargo.toml @@ -25,7 +25,8 @@ iron-test = "0.6" router = "0.6" mount = "0.4" logger = "0.4" -url = "1.7.1" +url = "2" +percent-encoding = "2" [dev-dependencies.juniper] version = "0.13.1" diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 20215fbb..678226fe 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -431,7 +431,7 @@ mod tests { use super::*; use iron::{Handler, Headers, Url}; use iron_test::{request, response}; - use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; + use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use juniper::{ http::tests as http_tests, @@ -441,6 +441,9 @@ mod tests { use super::GraphQLHandler; + /// https://url.spec.whatwg.org/#query-state + const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); + // This is ugly but it works. `iron_test` just dumps the path/url in headers // and newer `hyper` doesn't allow unescaped "{" or "}". fn fixup_url(url: &str) -> String { @@ -454,7 +457,7 @@ mod tests { format!( "http://localhost:3000{}?{}", path, - utf8_percent_encode(url.query().unwrap_or(""), DEFAULT_ENCODE_SET) + utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET) ) } @@ -521,7 +524,7 @@ mod tests { } } - fn make_handler() -> Box { + fn make_handler() -> Box { Box::new(GraphQLHandler::new( context_factory, Query, From c4d7661b3b4b4ec3124a6f8b8d2461f2680afd21 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:03:54 -0700 Subject: [PATCH 05/25] Release juniper_codegen 0.14.0 --- juniper/Cargo.toml | 2 +- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index a581977e..27dce1fe 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -32,7 +32,7 @@ default = [ ] [dependencies] -juniper_codegen = { version = "0.13.2", path = "../juniper_codegen" } +juniper_codegen = { version = "0.14.0", path = "../juniper_codegen" } fnv = "1.0.3" indexmap = { version = "1.0.0", features = ["serde-1"] } diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index fecf37cf..f504cc2b 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper_codegen" -version = "0.13.2" +version = "0.14.0" authors = [ "Magnus Hallin ", "Christoph Herzog ", diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 934a713c..d977fe02 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -4,7 +4,7 @@ //! You should not depend on juniper_codegen directly. //! You only need the `juniper` crate. -#![doc(html_root_url = "https://docs.rs/juniper_codegen/0.13.2")] +#![doc(html_root_url = "https://docs.rs/juniper_codegen/0.14.0")] #![recursion_limit = "1024"] extern crate proc_macro; From dbbceae64323bf499c79bcefb7b4181736ba7fc6 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:04:31 -0700 Subject: [PATCH 06/25] Release juniper 0.14.0 --- juniper/CHANGELOG.md | 4 ++++ juniper/Cargo.toml | 2 +- juniper/src/lib.rs | 2 +- juniper_codegen/Cargo.toml | 2 +- juniper_hyper/Cargo.toml | 4 ++-- juniper_iron/Cargo.toml | 4 ++-- juniper_rocket/Cargo.toml | 4 ++-- juniper_warp/Cargo.toml | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index c24b1c0b..3af51898 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,5 +1,9 @@ # master +- No changes yet + +# [[0.14.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.0) + - Require `url` 2.x if `url` feature is enabled. - Improve lookahead visitability. - Add ability to parse 'subscription'. diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 27dce1fe..b8176c46 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper" -version = "0.13.1" +version = "0.14.0" authors = [ "Magnus Hallin ", "Christoph Herzog ", diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 2b010a6a..434d7763 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -88,7 +88,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [chrono]: https://crates.io/crates/chrono */ -#![doc(html_root_url = "https://docs.rs/juniper/0.13.1")] +#![doc(html_root_url = "https://docs.rs/juniper/0.14.0")] #![warn(missing_docs)] #[doc(hidden)] diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index f504cc2b..fe4ed6ae 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -20,7 +20,7 @@ syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] } quote = "1.0.2" [dev-dependencies] -juniper = { version = "0.13.1", path = "../juniper" } +juniper = { version = "0.14.0", path = "../juniper" } [badges] travis-ci = { repository = "graphql-rust/juniper" } diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index 4017ee51..30c696b5 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -13,7 +13,7 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" url = "2" -juniper = { version = "0.13.1" , default-features = false, path = "../juniper"} +juniper = { version = "0.14.0", default-features = false, path = "../juniper"} futures = "0.1" tokio = "0.1.8" @@ -25,6 +25,6 @@ pretty_env_logger = "0.2" reqwest = "0.9" [dev-dependencies.juniper] -version = "0.13.1" +version = "0.14.0" features = ["expose-test-schema", "serde_json"] path = "../juniper" diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml index f6ac6661..a28f2f71 100644 --- a/juniper_iron/Cargo.toml +++ b/juniper_iron/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" serde = { version = "1.0.2" } serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } -juniper = { version = "0.13.1", path = "../juniper" } +juniper = { version = "0.14.0", path = "../juniper" } urlencoded = { version = ">= 0.5, < 0.7" } iron = ">= 0.5, < 0.7" @@ -29,6 +29,6 @@ url = "2" percent-encoding = "2" [dev-dependencies.juniper] -version = "0.13.1" +version = "0.14.0" features = ["expose-test-schema", "serde_json"] path = "../juniper" diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index 8fd1a618..e4d2a6ff 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -15,11 +15,11 @@ edition = "2018" serde = { version = "1.0.2" } serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } -juniper = { version = "0.13.1" , default-features = false, path = "../juniper"} +juniper = { version = "0.14.0", default-features = false, path = "../juniper"} rocket = { version = "0.4.0" } [dev-dependencies.juniper] -version = "0.13.1" +version = "0.14.0" features = ["expose-test-schema", "serde_json"] path = "../juniper" diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 1870a385..5387f99c 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] warp = "0.1.8" -juniper = { version = "0.13.1", path = "../juniper", default-features = false } +juniper = { version = "0.14.0", path = "../juniper", default-features = false } serde_json = "1.0.24" serde_derive = "1.0.75" failure = "0.1.2" @@ -19,7 +19,7 @@ serde = "1.0.75" tokio-threadpool = "0.1.7" [dev-dependencies] -juniper = { version = "0.13.1", path = "../juniper", features = ["expose-test-schema", "serde_json"] } +juniper = { version = "0.14.0", path = "../juniper", features = ["expose-test-schema", "serde_json"] } env_logger = "0.5.11" log = "0.4.3" percent-encoding = "1.0" From 03fca018a6ef84dce9daefa167a09fee17c5052c Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:05:37 -0700 Subject: [PATCH 07/25] Release juniper_hyper 0.5.0 --- juniper_hyper/CHANGELOG.md | 4 ++++ juniper_hyper/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/juniper_hyper/CHANGELOG.md b/juniper_hyper/CHANGELOG.md index ed180033..d51be2c4 100644 --- a/juniper_hyper/CHANGELOG.md +++ b/juniper_hyper/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.5.0) + +- Compatibility with the latest `juniper`. + # [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.4.1) - Compatibility with the latest `juniper`. diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index 30c696b5..28525061 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper_hyper" -version = "0.4.1" +version = "0.5.0" authors = ["Damir Vandic "] description = "Juniper GraphQL integration with Hyper" license = "BSD-2-Clause" From b208dd5bd672d16faf3bc2521e2ed7742f8e987b Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:07:21 -0700 Subject: [PATCH 08/25] Release juniper_iron 0.6.0 --- juniper_iron/CHANGELOG.md | 4 ++++ juniper_iron/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/juniper_iron/CHANGELOG.md b/juniper_iron/CHANGELOG.md index 1d9bc15c..60c8be81 100644 --- a/juniper_iron/CHANGELOG.md +++ b/juniper_iron/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +# [[0.6.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.6.0) + +- Compatibility with the latest `juniper`. + # [[0.5.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.5.1) - Compatibility with the latest `juniper`. diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml index a28f2f71..81ad40c2 100644 --- a/juniper_iron/Cargo.toml +++ b/juniper_iron/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper_iron" -version = "0.5.1" +version = "0.6.0" authors = [ "Magnus Hallin ", "Christoph Herzog ", From 2afec7f433d01f6b46fd352a94c54b22a104bfd1 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:09:06 -0700 Subject: [PATCH 09/25] Release juniper_rocket 0.5.0 --- juniper_rocket/CHANGELOG.md | 4 ++++ juniper_rocket/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md index 67cf230e..fd9e102e 100644 --- a/juniper_rocket/CHANGELOG.md +++ b/juniper_rocket/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.0) + +- Compatibility with the latest `juniper`. + # [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.4.1) - Compatibility with the latest `juniper`. diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index e4d2a6ff..aa3605a1 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper_rocket" -version = "0.4.1" +version = "0.5.0" authors = [ "Magnus Hallin ", "Christoph Herzog ", From 61c05435232644cd8d08ea46e973e2f8da2f4730 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 29 Sep 2019 19:11:04 -0700 Subject: [PATCH 10/25] Release juniper_warp 0.5.0 --- juniper_warp/CHANGELOG.md | 4 ++++ juniper_warp/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/juniper_warp/CHANGELOG.md b/juniper_warp/CHANGELOG.md index 46041b5b..eee06a3e 100644 --- a/juniper_warp/CHANGELOG.md +++ b/juniper_warp/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.5.0) + +- Compatibility with the latest `juniper`. + # [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.4.1) - Compatibility with the latest `juniper`. diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 5387f99c..75b28bb9 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "juniper_warp" -version = "0.4.1" +version = "0.5.0" authors = ["Tom Houlé "] description = "Juniper GraphQL integration with Warp" license = "BSD-2-Clause" From 2cf991697cbaf7610f667b1bfbff547f35aeca00 Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Thu, 10 Oct 2019 07:14:45 +0200 Subject: [PATCH 11/25] Upgrade futures-preview, tokio crates and remove unnecessary 'async_await' feature (#436) --- examples/warp_async/Cargo.toml | 2 +- examples/warp_async/src/main.rs | 2 +- integration_tests/async_await/Cargo.toml | 4 ++-- integration_tests/async_await/src/main.rs | 6 +++--- juniper/Cargo.toml | 4 ++-- juniper/src/lib.rs | 2 +- juniper_benchmarks/Cargo.toml | 4 ++-- juniper_benchmarks/src/lib.rs | 2 +- juniper_rocket/Cargo.toml | 3 ++- juniper_rocket/src/lib.rs | 15 ++++++--------- juniper_warp/Cargo.toml | 2 +- juniper_warp/src/lib.rs | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml index c33445e8..7568b799 100644 --- a/examples/warp_async/Cargo.toml +++ b/examples/warp_async/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" log = "0.4.8" env_logger = "0.6.2" warp = "0.1.19" -futures-preview = { version = "0.3.0-alpha.18", features = ["nightly", "async-await", "compat"] } +futures-preview = { version = "0.3.0-alpha.19", features = ["async-await", "compat"] } reqwest = "0.9.19" juniper_codegen = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } diff --git a/examples/warp_async/src/main.rs b/examples/warp_async/src/main.rs index 170d743b..6b53ff38 100644 --- a/examples/warp_async/src/main.rs +++ b/examples/warp_async/src/main.rs @@ -2,7 +2,7 @@ //! This example demonstrates async/await usage with warp. //! NOTE: this uses tokio 0.1 , not the alpha tokio 0.2. -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{EmptyMutation, RootNode, FieldError}; use warp::{http::Response, Filter}; diff --git a/integration_tests/async_await/Cargo.toml b/integration_tests/async_await/Cargo.toml index 3bbc0436..2207cf04 100644 --- a/integration_tests/async_await/Cargo.toml +++ b/integration_tests/async_await/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" [dependencies] juniper = { path = "../../juniper", features = ["async"] } -futures-preview = "0.3.0-alpha.18" -tokio = "0.2.0-alpha.2" +futures-preview = "=0.3.0-alpha.19" +tokio = "=0.2.0-alpha.6" diff --git a/integration_tests/async_await/src/main.rs b/integration_tests/async_await/src/main.rs index cbdfc4e3..eed72dff 100644 --- a/integration_tests/async_await/src/main.rs +++ b/integration_tests/async_await/src/main.rs @@ -1,4 +1,4 @@ -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{graphql_value, RootNode, Value}; @@ -38,7 +38,7 @@ impl User { async fn delayed() -> bool { let when = tokio::clock::now() + std::time::Duration::from_millis(100); - tokio::timer::Delay::new(when).await; + tokio::timer::delay(when).await; true } } @@ -65,7 +65,7 @@ impl Query { async fn delayed() -> bool { let when = tokio::clock::now() + std::time::Duration::from_millis(100); - tokio::timer::Delay::new(when).await; + tokio::timer::delay(when).await; true } } diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index e3bf3120..b7d62c94 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -45,9 +45,9 @@ serde_json = { version="1.0.2", optional = true } url = { version = "1.5.1", optional = true } uuid = { version = "0.7", optional = true } -futures-preview = { version = "0.3.0-alpha.18", optional = true, features = ["nightly", "async-await"] } +futures-preview = { version = "=0.3.0-alpha.19", optional = true } [dev-dependencies] bencher = "0.1.2" serde_json = { version = "1.0.2" } -tokio = "0.2.0-alpha.2" +tokio = "=0.2.0-alpha.6" diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index ca0a4dc5..c4510863 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -91,7 +91,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. #![doc(html_root_url = "https://docs.rs/juniper/0.13.1")] #![warn(missing_docs)] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] #[doc(hidden)] pub extern crate serde; diff --git a/juniper_benchmarks/Cargo.toml b/juniper_benchmarks/Cargo.toml index e4da5478..df10da20 100644 --- a/juniper_benchmarks/Cargo.toml +++ b/juniper_benchmarks/Cargo.toml @@ -12,8 +12,8 @@ harness = false [dependencies] juniper = { path = "../juniper", features = ["async"] } -futures-preview = "0.3.0-alpha.18" +futures-preview = "=0.3.0-alpha.19" [dev-dependencies] criterion = "0.2.11" -tokio = "0.2.0-alpha.2" +tokio = "=0.2.0-alpha.6" diff --git a/juniper_benchmarks/src/lib.rs b/juniper_benchmarks/src/lib.rs index a9f2aa4b..65c3b20a 100644 --- a/juniper_benchmarks/src/lib.rs +++ b/juniper_benchmarks/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{ object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables, diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index 737aca70..cad056bc 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -20,8 +20,9 @@ serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } juniper = { version = "0.13.1" , default-features = false, path = "../juniper"} -futures03 = { version = "0.3.0-alpha.18", package = "futures-preview", features = ["compat"] } +futures03 = { version = "=0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } +tokio = "=0.2.0-alpha.6" [dev-dependencies.juniper] version = "0.13.1" diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 88cf619d..68817cb1 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -38,18 +38,15 @@ Check the LICENSE file for details. #![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")] #![feature(decl_macro, proc_macro_hygiene)] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] -use std::{ - error::Error, - io::{Cursor, Read}, -}; +use std::{error::Error, io::Cursor}; use rocket::{ - data::{FromDataSimple, Outcome as FromDataOutcome}, + data::{FromDataFuture, FromDataSimple}, http::{ContentType, RawStr, Status}, request::{FormItems, FromForm, FromFormValue}, - response::{content, Responder, Response}, + response::{content, Responder, Response, ResultFuture}, Data, Outcome::{Failure, Forward, Success}, Request, @@ -400,8 +397,8 @@ where type Error = String; fn from_data(request: &Request, data: Data) -> FromDataFuture<'static, Self, Self::Error> { - use futures03::io::AsyncReadExt; - use rocket::AsyncReadExt as _; + use tokio::io::AsyncReadExt as _; + if !request.content_type().map_or(false, |ct| ct.is_json()) { return Box::pin(async move { Forward(data) }); } diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 04805c21..72b48689 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.1.23" serde = "1.0.75" tokio-threadpool = "0.1.7" -futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview", features = ["compat"] } +futures03 = { version = "=0.3.0-alpha.19", optional = true, package = "futures-preview", features = ["compat"] } [dev-dependencies] juniper = { version = "0.13.1", path = "../juniper", features = ["expose-test-schema", "serde_json"] } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index a8725b29..b00a9b6c 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -39,7 +39,7 @@ Check the LICENSE file for details. #![deny(missing_docs)] #![deny(warnings)] #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] use futures::{future::poll_fn, Future}; use serde::Deserialize; From 56a4f2558a174e560b6c1bbc82dc4290702567ab Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Sun, 18 Aug 2019 21:36:44 +0200 Subject: [PATCH 12/25] WIP async/await implementation --- Cargo.toml | 1 + integration_tests/async_await/Cargo.toml | 12 + integration_tests/async_await/src/main.rs | 126 +++++++ juniper/Cargo.toml | 4 + juniper/src/executor/mod.rs | 168 ++++++++- juniper/src/executor_tests/async_await/mod.rs | 120 +++++++ juniper/src/executor_tests/mod.rs | 8 +- juniper/src/lib.rs | 48 ++- juniper/src/macros/common.rs | 73 +++- juniper/src/macros/scalar.rs | 328 ++++++++++++++++++ juniper/src/schema/schema.rs | 44 +++ juniper/src/types/async_await.rs | 281 +++++++++++++++ juniper/src/types/base.rs | 9 +- juniper/src/types/containers.rs | 103 ++++++ juniper/src/types/mod.rs | 3 + juniper/src/types/pointers.rs | 31 +- juniper/src/types/scalars.rs | 17 + juniper/src/value/mod.rs | 7 + juniper/src/value/object.rs | 13 + juniper/src/value/scalar.rs | 2 + juniper_codegen/Cargo.toml | 3 + juniper_codegen/src/derive_enum.rs | 29 +- juniper_codegen/src/derive_object.rs | 2 + juniper_codegen/src/impl_object.rs | 32 +- juniper_codegen/src/util.rs | 172 ++++++++- juniper_warp/Cargo.toml | 5 + juniper_warp/src/lib.rs | 77 +++- 27 files changed, 1681 insertions(+), 37 deletions(-) create mode 100644 integration_tests/async_await/Cargo.toml create mode 100644 integration_tests/async_await/src/main.rs create mode 100644 juniper/src/executor_tests/async_await/mod.rs create mode 100644 juniper/src/types/async_await.rs diff --git a/Cargo.toml b/Cargo.toml index 705cd8f1..35fbad11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "juniper_codegen", "juniper", "integration_tests/juniper_tests", + "integration_tests/async_await", "juniper_hyper", "juniper_iron", "juniper_rocket", diff --git a/integration_tests/async_await/Cargo.toml b/integration_tests/async_await/Cargo.toml new file mode 100644 index 00000000..3bbc0436 --- /dev/null +++ b/integration_tests/async_await/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "async_await" +version = "0.1.0" +authors = ["Christoph Herzog "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +juniper = { path = "../../juniper", features = ["async"] } +futures-preview = "0.3.0-alpha.18" +tokio = "0.2.0-alpha.2" diff --git a/integration_tests/async_await/src/main.rs b/integration_tests/async_await/src/main.rs new file mode 100644 index 00000000..cbdfc4e3 --- /dev/null +++ b/integration_tests/async_await/src/main.rs @@ -0,0 +1,126 @@ +#![feature(async_await, async_closure)] + +use juniper::{graphql_value, RootNode, Value}; + +#[derive(juniper::GraphQLEnum)] +enum UserKind { + Admin, + User, + Guest, +} + +struct User { + id: u64, + name: String, + kind: UserKind, +} + +#[juniper::object] +impl User { + async fn name(&self) -> &str { + &self.name + } + + async fn friends(&self) -> Vec { + let friends = (0..10) + .map(|index| User { + id: index, + name: format!("user{}", index), + kind: UserKind::User, + }) + .collect(); + friends + } + + async fn kind(&self) -> &UserKind { + &self.kind + } + + async fn delayed() -> bool { + let when = tokio::clock::now() + std::time::Duration::from_millis(100); + tokio::timer::Delay::new(when).await; + true + } +} + +struct Query; + +#[juniper::object] +impl Query { + fn field_sync(&self) -> &'static str { + "field_sync" + } + + async fn field_async_plain() -> String { + "field_async_plain".to_string() + } + + fn user(id: String) -> User { + User { + id: 1, + name: id, + kind: UserKind::User, + } + } + + async fn delayed() -> bool { + let when = tokio::clock::now() + std::time::Duration::from_millis(100); + tokio::timer::Delay::new(when).await; + true + } +} + +struct Mutation; + +#[juniper::object] +impl Mutation {} + +fn run(f: impl std::future::Future) -> O { + tokio::runtime::current_thread::Runtime::new() + .unwrap() + .block_on(f) +} + +#[test] +fn async_simple() { + let schema = RootNode::new(Query, Mutation); + let doc = r#" + query { + fieldSync + fieldAsyncPlain + delayed + user(id: "user1") { + kind + name + delayed + } + } + "#; + + let vars = Default::default(); + let f = juniper::execute_async(doc, None, &schema, &vars, &()); + + let (res, errs) = run(f).unwrap(); + + assert!(errs.is_empty()); + + let mut obj = res.into_object().unwrap(); + obj.sort_by_field(); + let value = Value::Object(obj); + + assert_eq!( + value, + graphql_value!({ + "delayed": true, + "fieldAsyncPlain": "field_async_plain", + "fieldSync": "field_sync", + "user": { + "delayed": true, + "kind": "USER", + "name": "user1", + }, + }), + ); +} + +fn main() {} diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index b8176c46..7a57180d 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -24,6 +24,7 @@ harness = false path = "benches/bench.rs" [features] +async = ["juniper_codegen/async", "futures-preview"] expose-test-schema = [] default = [ "chrono", @@ -44,6 +45,9 @@ serde_json = { version="1.0.2", optional = true } url = { version = "2", optional = true } uuid = { version = "0.7", optional = true } +futures-preview = { version = "0.3.0-alpha.18", optional = true, features = ["nightly", "async-await"] } + [dev-dependencies] bencher = "0.1.2" serde_json = { version = "1.0.2" } +tokio = "0.2.0-alpha.2" diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index d7489393..8b96b9ac 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -372,6 +372,37 @@ where Ok(value.resolve(info, self.current_selection_set, self)) } + /// Resolve a single arbitrary value into an `ExecutionResult` + #[cfg(feature = "async")] + pub async fn resolve_async(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult + where + T: crate::GraphQLTypeAsync + Send + Sync, + T::TypeInfo: Send + Sync, + CtxT: Send + Sync, + S: Send + Sync, + { + Ok(value + .resolve_async(info, self.current_selection_set, self) + .await) + } + + /// Resolve a single arbitrary value, mapping the context to a new type + #[cfg(feature = "async")] + pub async fn resolve_with_ctx_async( + &self, + info: &T::TypeInfo, + value: &T, + ) -> ExecutionResult + where + T: crate::GraphQLTypeAsync + Send + Sync, + T::TypeInfo: Send + Sync, + S: Send + Sync, + NewCtxT: FromContext + Send + Sync, + { + let e = self.replaced_context(>::from(self.context)); + e.resolve_async(info, value).await + } + /// Resolve a single arbitrary value into a return value /// /// If the field fails to resolve, `null` will be returned. @@ -388,6 +419,26 @@ where } } + /// Resolve a single arbitrary value into a return value + /// + /// If the field fails to resolve, `null` will be returned. + #[cfg(feature = "async")] + pub async fn resolve_into_value_async(&self, info: &T::TypeInfo, value: &T) -> Value + where + T: crate::GraphQLTypeAsync + Send + Sync, + T::TypeInfo: Send + Sync, + CtxT: Send + Sync, + S: Send + Sync, + { + match self.resolve_async(info, value).await { + Ok(v) => v, + Err(e) => { + self.push_error(e); + Value::null() + } + } + } + /// Derive a new executor by replacing the context /// /// This can be used to connect different types, e.g. from different Rust @@ -480,7 +531,7 @@ where } #[doc(hidden)] - pub fn fragment_by_name(&self, name: &str) -> Option<&'a Fragment> { + pub fn fragment_by_name(&'a self, name: &str) -> Option<&'a Fragment<'a, S>> { self.fragments.get(name).cloned() } @@ -720,6 +771,121 @@ where Ok((value, errors)) } +#[cfg(feature = "async")] +pub async fn execute_validated_query_async<'a, QueryT, MutationT, CtxT, S>( + document: Document<'a, S>, + operation_name: Option<&str>, + root_node: &RootNode<'a, QueryT, MutationT, S>, + variables: &Variables, + context: &CtxT, +) -> Result<(Value, Vec>), GraphQLError<'a>> +where + S: ScalarValue + Send + Sync, + QueryT: crate::GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: crate::GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + let mut fragments = vec![]; + let mut operation = None; + + for def in document { + match def { + Definition::Operation(op) => { + if operation_name.is_none() && operation.is_some() { + return Err(GraphQLError::MultipleOperationsProvided); + } + + let move_op = operation_name.is_none() + || op.item.name.as_ref().map(|s| s.item) == operation_name; + + if move_op { + operation = Some(op); + } + } + Definition::Fragment(f) => fragments.push(f), + }; + } + + let op = match operation { + Some(op) => op, + None => return Err(GraphQLError::UnknownOperationName), + }; + + let default_variable_values = op.item.variable_definitions.map(|defs| { + defs.item + .items + .iter() + .filter_map(|&(ref name, ref def)| { + def.default_value + .as_ref() + .map(|i| (name.item.to_owned(), i.item.clone())) + }) + .collect::>>() + }); + + let errors = RwLock::new(Vec::new()); + let value; + + { + let mut all_vars; + let mut final_vars = variables; + + if let Some(defaults) = default_variable_values { + all_vars = variables.clone(); + + for (name, value) in defaults { + all_vars.entry(name).or_insert(value); + } + + final_vars = &all_vars; + } + + let root_type = match op.item.operation_type { + OperationType::Query => root_node.schema.query_type(), + OperationType::Mutation => root_node + .schema + .mutation_type() + .expect("No mutation type found"), + }; + + let executor = Executor { + fragments: &fragments + .iter() + .map(|f| (f.item.name.item, &f.item)) + .collect(), + variables: final_vars, + current_selection_set: Some(&op.item.selection_set[..]), + parent_selection_set: None, + current_type: root_type, + schema: &root_node.schema, + context, + errors: &errors, + field_path: FieldPath::Root(op.start), + }; + + value = match op.item.operation_type { + OperationType::Query => { + executor + .resolve_into_value_async(&root_node.query_info, &root_node) + .await + } + OperationType::Mutation => { + executor + .resolve_into_value_async(&root_node.mutation_info, &root_node.mutation_type) + .await + } + }; + } + + let mut errors = errors.into_inner().unwrap(); + errors.sort(); + + Ok((value, errors)) +} + impl<'r, S> Registry<'r, S> where S: ScalarValue + 'r, diff --git a/juniper/src/executor_tests/async_await/mod.rs b/juniper/src/executor_tests/async_await/mod.rs new file mode 100644 index 00000000..cd4771c5 --- /dev/null +++ b/juniper/src/executor_tests/async_await/mod.rs @@ -0,0 +1,120 @@ +use crate::{RootNode, Value}; + +#[derive(crate::GraphQLEnumInternal)] +enum UserKind { + Admin, + User, + Guest, +} + +struct User { + id: u64, + name: String, + kind: UserKind, +} + +#[crate::object_internal] +impl User { + async fn name(&self) -> &str { + &self.name + } + + async fn friends(&self) -> Vec { + let friends = (0..10) + .map(|index| User { + id: index, + name: format!("user{}", index), + kind: UserKind::User, + }) + .collect(); + friends + } + + async fn kind(&self) -> &UserKind { + &self.kind + } + + async fn delayed() -> bool { + let when = tokio::clock::now() + std::time::Duration::from_millis(100); + tokio::timer::Delay::new(when).await; + true + } +} + +struct Query; + +#[crate::object_internal] +impl Query { + fn field_sync(&self) -> &'static str { + "field_sync" + } + + async fn field_async_plain() -> String { + "field_async_plain".to_string() + } + + fn user(id: String) -> User { + User { + id: 1, + name: id, + kind: UserKind::User, + } + } + + async fn delayed() -> bool { + let when = tokio::clock::now() + std::time::Duration::from_millis(100); + tokio::timer::Delay::new(when).await; + true + } +} + +struct Mutation; + +#[crate::object_internal] +impl Mutation {} + +fn run(f: impl std::future::Future) -> O { + tokio::runtime::current_thread::Runtime::new() + .unwrap() + .block_on(f) +} + +#[test] +fn async_simple() { + let schema = RootNode::new(Query, Mutation); + let doc = r#" + query { + fieldSync + fieldAsyncPlain + delayed + user(id: "user1") { + name + } + } + "#; + + let vars = Default::default(); + let f = crate::execute_async(doc, None, &schema, &vars, &()); + + let (res, errs) = run(f).unwrap(); + + assert!(errs.is_empty()); + + let mut obj = res.into_object().unwrap(); + obj.sort_by_field(); + let value = Value::Object(obj); + + assert_eq!( + value, + crate::graphql_value!({ + "delayed": true, + "fieldAsyncPlain": "field_async_plain", + "fieldSync": "field_sync", + "user": { + "kind": "USER", + // "name": "user1", + // "delayed": true, + }, + }), + ); +} diff --git a/juniper/src/executor_tests/mod.rs b/juniper/src/executor_tests/mod.rs index 01097618..aefac036 100644 --- a/juniper/src/executor_tests/mod.rs +++ b/juniper/src/executor_tests/mod.rs @@ -1,6 +1,12 @@ mod directives; mod enums; mod executor; -mod interfaces_unions; mod introspection; mod variables; + +// FIXME: re-enable +#[cfg(not(feature = "async"))] +mod interfaces_unions; + +#[cfg(feature = "async")] +mod async_await; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 434d7763..95ce987b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -90,6 +90,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. */ #![doc(html_root_url = "https://docs.rs/juniper/0.14.0")] #![warn(missing_docs)] +#![cfg_attr(feature = "async", feature(async_await, async_closure))] #[doc(hidden)] pub extern crate serde; @@ -150,7 +151,6 @@ mod executor_tests; pub use crate::util::to_camel_case; use crate::{ - executor::execute_validated_query, introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS}, parser::{parse_document_source, ParseError, Spanning}, validation::{validate_input_values, visit_all_rules, ValidatorContext}, @@ -176,6 +176,9 @@ pub use crate::{ }, }; +#[cfg(feature = "async")] +pub use crate::types::async_await::GraphQLTypeAsync; + /// An error that prevented query execution #[derive(Debug, PartialEq)] #[allow(missing_docs)] @@ -221,7 +224,48 @@ where } } - execute_validated_query(document, operation_name, root_node, variables, context) + executor::execute_validated_query(document, operation_name, root_node, variables, context) +} + +/// Execute a query in a provided schema +#[cfg(feature = "async")] +pub async fn execute_async<'a, S, CtxT, QueryT, MutationT>( + document_source: &'a str, + operation_name: Option<&str>, + root_node: &'a RootNode<'a, QueryT, MutationT, S>, + variables: &Variables, + context: &CtxT, +) -> Result<(Value, Vec>), GraphQLError<'a>> +where + S: ScalarValue + Send + Sync, + QueryT: GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + let document = parse_document_source(document_source, &root_node.schema)?; + { + let errors = validate_input_values(variables, &document, &root_node.schema); + + if !errors.is_empty() { + return Err(GraphQLError::ValidationError(errors)); + } + } + + { + let mut ctx = ValidatorContext::new(&root_node.schema, &document); + visit_all_rules(&mut ctx, &document); + + let errors = ctx.into_errors(); + if !errors.is_empty() { + return Err(GraphQLError::ValidationError(errors)); + } + } + + executor::execute_validated_query_async(document, operation_name, root_node, variables, context) + .await } /// Execute the reference introspection query in the provided schema diff --git a/juniper/src/macros/common.rs b/juniper/src/macros/common.rs index 416686a2..9a6ddef3 100644 --- a/juniper/src/macros/common.rs +++ b/juniper/src/macros/common.rs @@ -7,29 +7,73 @@ macro_rules! __juniper_impl_trait { } ) => { impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name { - $($body)+ + $($body)* } }; ( - impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty { + impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty + where ( $($where:tt)* ) + { $($body:tt)+ } + ) => { + impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name + where $($where)* + { + $($body)* + } + }; + + ( + impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty { + $($body:tt)* + } ) => { impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name where $generic: $crate::ScalarValue, for<'__b> &'__b $generic: $crate::ScalarRefValue<'__b>, { - $($body)+ + $($body)* } }; + ( + impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty + where ( $($where:tt)* ) + { + $($body:tt)* + } + ) => { + impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name + where + $($where)* + $generic: $crate::ScalarValue, + for<'__b> &'__b $generic: $crate::ScalarRefValue<'__b>, + { + $($body)* + } + }; + ( impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty { - $($body:tt)+ + $($body:tt)* } ) => { impl<$($other, )*> $crate::$impl_trait<$scalar> for $name { - $($body)+ + $($body)* + } + }; + ( + impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty + where ( $($where:tt)* ) + { + $($body:tt)* + } + ) => { + impl<$($other, )*> $crate::$impl_trait<$scalar> for $name + where $($where)* + { + $($body)* } }; } @@ -52,6 +96,25 @@ macro_rules! __juniper_insert_generic { }; } +// TODO: remove me. +#[doc(hidden)] +#[macro_export] +macro_rules! __juniper_extract_generic { + (<$name:ident>) => { + $name + }; + ( + <$generic:tt $(: $bound: tt)*> + ) => { + $generic + }; + ( + $scalar: ty + ) => { + $scalar + }; +} + #[doc(hidden)] #[macro_export] macro_rules! __juniper_parse_object_header { diff --git a/juniper/src/macros/scalar.rs b/juniper/src/macros/scalar.rs index 56537072..50753f3b 100644 --- a/juniper/src/macros/scalar.rs +++ b/juniper/src/macros/scalar.rs @@ -45,9 +45,12 @@ In addition to implementing `GraphQLType` for the type in question, usable as arguments and default values. */ + +#[cfg(not(feature = "async"))] #[macro_export] macro_rules! graphql_scalar { ( @as_expr $e:expr) => { $e }; + ( @generate, meta = { @@ -341,3 +344,328 @@ macro_rules! graphql_scalar { ); } } + +// FIXME: prevent duplicating the whole macro for async. +#[cfg(feature = "async")] +#[macro_export] +macro_rules! graphql_scalar { + ( @as_expr $e:expr) => { $e }; + + ( + @generate, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + $(description = $descr:tt,)* + }, + resolve = { + self_var = $resolve_self_var:ident, + body = $resolve_body: block, + return_type = $resolve_retun_type: ty, + }, + from_input_value = { + arg = $from_input_value_arg: ident, + result = $from_input_value_result: ty, + body = $from_input_value_body: block, + }, + from_str = { + value_arg = $from_str_arg: ident, + result = $from_str_result: ty, + body = $from_str_body: block, + lifetime = $from_str_lt: tt, + }, + + ) => { + $crate::__juniper_impl_trait!( + impl <$($scalar)+> GraphQLType for $name { + type Context = (); + type TypeInfo = (); + + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some($crate::graphql_scalar!(@as_expr $($outname)+)) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> + ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> + where for<'__b> &'__b $crate::__juniper_insert_generic!($($scalar)+): $crate::ScalarRefValue<'__b>, + $crate::__juniper_insert_generic!($($scalar)+): 'r + { + let meta = registry.build_scalar_type::(info); + $( + let meta = meta.description($descr); + )* + meta.into_meta() + } + + fn resolve( + &$resolve_self_var, + _: &(), + _: Option<&[$crate::Selection<$crate::__juniper_insert_generic!($($scalar)+)>]>, + _: &$crate::Executor< + Self::Context, + $crate::__juniper_insert_generic!($($scalar)+) + >) -> $crate::Value<$crate::__juniper_insert_generic!($($scalar)+)> { + $resolve_body + } + } + ); + + $crate::__juniper_impl_trait!( + impl <$($scalar)+> GraphQLTypeAsync for $name + where ( + $crate::__juniper_insert_generic!($($scalar)+): Send + Sync, + Self: $crate::GraphQLType<$crate::__juniper_insert_generic!($($scalar)+)> + Send + Sync, + Self::Context: Send + Sync, + Self::TypeInfo: Send + Sync, + ) + { + + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [$crate::Selection<$crate::__juniper_insert_generic!($($scalar)+)>]>, + executor: &'a $crate::Executor, + ) -> futures::future::BoxFuture<'a, $crate::Value<$crate::__juniper_insert_generic!($($scalar)+)>> { + use $crate::GraphQLType; + use futures::future; + let v = self.resolve(info, selection_set, executor); + future::FutureExt::boxed(future::ready(v)) + } + } + ); + + $crate::__juniper_impl_trait!( + impl<$($scalar)+> ToInputValue for $name { + fn to_input_value(&$resolve_self_var) -> $crate::InputValue<$crate::__juniper_insert_generic!($($scalar)+)> { + let v = $resolve_body; + $crate::ToInputValue::to_input_value(&v) + } + } + ); + + $crate::__juniper_impl_trait!( + impl<$($scalar)+> FromInputValue for $name { + fn from_input_value( + $from_input_value_arg: &$crate::InputValue<$crate::__juniper_insert_generic!($($scalar)+)> + ) -> $from_input_value_result { + $from_input_value_body + } + } + ); + + $crate::__juniper_impl_trait!( + impl<$($scalar)+> ParseScalarValue for $name { + fn from_str<$from_str_lt>($from_str_arg: $crate::parser::ScalarToken<$from_str_lt>) -> $from_str_result { + $from_str_body + } + } + ); + }; + + // No more items to parse + ( + @parse_functions, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + $(description = $descr:tt,)* + }, + resolve = {$($resolve_body:tt)+}, + from_input_value = {$($from_input_value_body:tt)+}, + from_str = {$($from_str_body:tt)+}, + rest = + ) => { + $crate::graphql_scalar!( + @generate, + meta = { + name = $name, + outname = {$($outname)+}, + scalar = {$($scalar)+}, + $(description = $descr,)* + }, + resolve = {$($resolve_body)+}, + from_input_value = {$($from_input_value_body)+}, + from_str = {$($from_str_body)+}, + ); + }; + + ( + @parse_functions, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + $(description = $descr:tt,)* + }, + $(from_input_value = {$($from_input_value_body:tt)+})*, + $(from_str = {$($from_str_body:tt)+})*, + rest = + ) => { + compile_error!("Missing resolve function"); + }; + + ( + @parse_functions, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + $(description = $descr:tt,)* + }, + resolve = {$($resolve_body:tt)+}, + $(from_str = {$($from_str_body:tt)+})*, + rest = + ) => { + compile_error!("Missing from_input_value function"); + }; + + ( + @parse_functions, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + $(description = $descr:tt,)* + }, + resolve = {$($resolve_body:tt)+}, + from_input_value = {$($from_input_value_body:tt)+}, + rest = + ) =>{ + compile_error!("Missing from_str function"); + }; + + + // resolve(&self) -> Value { ... } + ( + @parse_functions, + meta = {$($meta:tt)*}, + $(resolve = {$($resolve_body:tt)+},)* + $(from_input_value = {$($from_input_value_body:tt)+},)* + $(from_str = {$($from_str_body:tt)+},)* + rest = resolve(&$selfvar:ident) -> $return_ty:ty $body:block $($rest:tt)* + ) => { + $crate::graphql_scalar!( + @parse_functions, + meta = {$($meta)*}, + resolve = { + self_var = $selfvar, + body = $body, + return_type = $return_ty, + }, + $(from_input_value = {$($from_input_value_body)+},)* + $(from_str = {$($from_str_body)+},)* + rest = $($rest)* + ); + }; + + // from_input_value(arg: &InputValue) -> ... { ... } + ( + @parse_functions, + meta = { $($meta:tt)* }, + $(resolve = {$($resolve_body:tt)+})*, + $(from_input_value = {$($from_input_value_body:tt)+},)* + $(from_str = {$($from_str_body:tt)+},)* + rest = from_input_value($arg:ident: &InputValue) -> $result:ty $body:block $($rest:tt)* + ) => { + $crate::graphql_scalar!( + @parse_functions, + meta = { $($meta)* }, + $(resolve = {$($resolve_body)+},)* + from_input_value = { + arg = $arg, + result = $result, + body = $body, + }, + $(from_str = {$($from_str_body)+},)* + rest = $($rest)* + ); + }; + + // from_str(value: &str) -> Result + ( + @parse_functions, + meta = { $($meta:tt)* }, + $(resolve = {$($resolve_body:tt)+},)* + $(from_input_value = {$($from_input_value_body:tt)+},)* + $(from_str = {$($from_str_body:tt)+},)* + rest = from_str<$from_str_lt: tt>($value_arg:ident: ScalarToken<$ignored_lt2: tt>) -> $result:ty $body:block $($rest:tt)* + ) => { + $crate::graphql_scalar!( + @parse_functions, + meta = { $($meta)* }, + $(resolve = {$($resolve_body)+},)* + $(from_input_value = {$($from_input_value_body)+},)* + from_str = { + value_arg = $value_arg, + result = $result, + body = $body, + lifetime = $from_str_lt, + }, + rest = $($rest)* + ); + }; + + // description: + ( + @parse_functions, + meta = { + name = $name:ty, + outname = {$($outname:tt)+}, + scalar = {$($scalar:tt)+}, + }, + $(resolve = {$($resolve_body:tt)+},)* + $(from_input_value = {$($from_input_value_body:tt)+},)* + $(from_str = {$($from_str_body:tt)+},)* + rest = description: $descr:tt $($rest:tt)* + ) => { + $crate::graphql_scalar!( + @parse_functions, + meta = { + name = $name, + outname = {$($outname)+}, + scalar = {$($scalar)+}, + description = $descr, + }, + $(resolve = {$($resolve_body)+},)* + $(from_input_value = {$($from_input_value_body)+},)* + $(from_str = {$($from_str_body)+},)* + rest = $($rest)* + ); + }; + + ( + @parse, + meta = { + lifetimes = [], + name = $name: ty, + outname = {$($outname:tt)*}, + scalar = {$($scalar:tt)*}, + }, + rest = $($rest:tt)* + ) => { + $crate::graphql_scalar!( + @parse_functions, + meta = { + name = $name, + outname = {$($outname)*}, + scalar = {$($scalar)*}, + }, + rest = $($rest)* + ); + }; + + (@$($stuff:tt)*) => { + compile_error!("Invalid syntax for `graphql_scalar!`"); + }; + + ($($rest:tt)*) => { + $crate::__juniper_parse_object_header!( + callback = graphql_scalar, + rest = $($rest)* + ); + } +} diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index a273fa7e..49b6940d 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -76,10 +76,44 @@ where } } +#[cfg(feature = "async")] +impl<'a, CtxT, S, QueryT, MutationT> crate::GraphQLTypeAsync + for RootNode<'a, QueryT, MutationT, S> +where + S: ScalarValue + Send + Sync, + QueryT: crate::GraphQLTypeAsync, + QueryT::TypeInfo: Send + Sync, + MutationT: crate::GraphQLTypeAsync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field_name: &'b str, + arguments: &'b Arguments, + executor: &'b Executor, + ) -> futures::future::BoxFuture<'b, ExecutionResult> { + use futures::future::{ready, FutureExt}; + match field_name { + "__schema" | "__type" => { + let v = self.resolve_field(info, field_name, arguments, executor); + ready(v).boxed() + } + _ => self + .query_type + .resolve_field_async(info, field_name, arguments, executor), + } + } +} + #[crate::object_internal( name = "__Schema" Context = SchemaType<'a, S>, Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> SchemaType<'a, S> where @@ -117,6 +151,8 @@ where name = "__Type" Context = SchemaType<'a, S>, Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> TypeType<'a, S> where @@ -248,6 +284,8 @@ where name = "__Field", Context = SchemaType<'a, S>, Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> Field<'a, S> where @@ -285,6 +323,8 @@ where name = "__InputValue", Context = SchemaType<'a, S>, Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> Argument<'a, S> where @@ -311,6 +351,8 @@ where #[crate::object_internal( name = "__EnumValue", Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> EnumValue where @@ -337,6 +379,8 @@ where name = "__Directive", Context = SchemaType<'a, S>, Scalar = S, + // FIXME: make this redundant. + noasync, )] impl<'a, S> DirectiveType<'a, S> where diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs new file mode 100644 index 00000000..05fb502c --- /dev/null +++ b/juniper/src/types/async_await.rs @@ -0,0 +1,281 @@ +use futures::future::BoxFuture; + +use crate::ast::{Directive, FromInputValue, InputValue, Selection}; +use crate::value::{Object, ScalarRefValue, ScalarValue, Value}; + +use crate::executor::{ExecutionResult, Executor}; +use crate::parser::Spanning; + +use super::base::{is_excluded, merge_key_into, Arguments, GraphQLType}; + +pub trait GraphQLTypeAsync: GraphQLType + Send + Sync +where + Self::Context: Send + Sync, + Self::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_field_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + field_name: &'a str, + arguments: &'a Arguments, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, ExecutionResult> { + panic!("resolve_field must be implemented by object types"); + } + + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, Value> { + if let Some(selection_set) = selection_set { + resolve_selection_set_into_async(self, info, selection_set, executor) + } else { + panic!("resolve() must be implemented by non-object output types"); + } + } +} + +// Wrapper function around resolve_selection_set_into_async_recursive. +// This wrapper is necessary because async fns can not be recursive. +#[cfg(feature = "async")] +pub(crate) fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>( + instance: &'a T, + info: &'a T::TypeInfo, + selection_set: &'e [Selection<'e, S>], + executor: &'e Executor<'e, CtxT, S>, +) -> futures::future::BoxFuture<'a, Value> +where + T: GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + CtxT: Send + Sync, + 'e: 'a, + for<'b> &'b S: ScalarRefValue<'b>, +{ + use futures::future::FutureExt; + + resolve_selection_set_into_async_recursive(instance, info, selection_set, executor).boxed() +} + +struct AsyncField { + name: String, + value: Option>, +} + +enum AsyncValue { + Field(AsyncField), + Nested(Value), +} + +// type ResolveFuture<'a, S> = BoxFuture<'a, AsyncResolve>; + +#[cfg(feature = "async")] +pub(crate) async fn resolve_selection_set_into_async_recursive<'a, T, CtxT, S>( + instance: &'a T, + info: &'a T::TypeInfo, + selection_set: &'a [Selection<'a, S>], + executor: &'a Executor<'a, CtxT, S>, +) -> Value +where + T: GraphQLTypeAsync + Send + Sync, + T::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + use futures::{ + future::FutureExt, + stream::{FuturesOrdered, StreamExt}, + }; + + let mut object = Object::with_capacity(selection_set.len()); + + let mut async_values = FuturesOrdered::>>::new(); + + let meta_type = executor + .schema() + .concrete_type_by_name( + T::name(info) + .expect("Resolving named type's selection set") + .as_ref(), + ) + .expect("Type not found in schema"); + + for selection in selection_set { + match *selection { + Selection::Field(Spanning { + item: ref f, + start: ref start_pos, + .. + }) => { + if is_excluded(&f.directives, executor.variables()) { + continue; + } + + let response_name = f.alias.as_ref().unwrap_or(&f.name).item; + + if f.name.item == "__typename" { + object.add_field( + response_name, + Value::scalar(instance.concrete_type_name(executor.context(), info)), + ); + continue; + } + + let meta_field = meta_type.field_by_name(f.name.item).unwrap_or_else(|| { + panic!(format!( + "Field {} not found on type {:?}", + f.name.item, + meta_type.name() + )) + }); + + let exec_vars = executor.variables(); + + let sub_exec = executor.field_sub_executor( + &response_name, + f.name.item, + start_pos.clone(), + f.selection_set.as_ref().map(|v| &v[..]), + ); + let args = Arguments::new( + f.arguments.as_ref().map(|m| { + m.item + .iter() + .map(|&(ref k, ref v)| (k.item, v.item.clone().into_const(exec_vars))) + .collect() + }), + &meta_field.arguments, + ); + + let pos = start_pos.clone(); + let is_non_null = meta_field.field_type.is_non_null(); + + let response_name = response_name.to_string(); + let field_future = async move { + // TODO: implement custom future type instead of + // two-level boxing. + let res = instance + .resolve_field_async(info, f.name.item, &args, &sub_exec) + .await; + + let value = match res { + Ok(Value::Null) if is_non_null => None, + Ok(v) => Some(v), + Err(e) => { + sub_exec.push_error_at(e, pos); + + if is_non_null { + None + } else { + Some(Value::null()) + } + } + }; + AsyncValue::Field(AsyncField { + name: response_name, + value, + }) + }; + async_values.push(field_future.boxed()); + } + Selection::FragmentSpread(Spanning { + item: ref spread, .. + }) => { + if is_excluded(&spread.directives, executor.variables()) { + continue; + } + + // TODO: prevent duplicate boxing. + let f = async move { + let fragment = &executor + .fragment_by_name(spread.name.item) + .expect("Fragment could not be found"); + let value = resolve_selection_set_into_async( + instance, + info, + &fragment.selection_set[..], + executor, + ) + .await; + AsyncValue::Nested(value) + }; + async_values.push(f.boxed()); + } + Selection::InlineFragment(Spanning { + item: ref fragment, + start: ref start_pos, + .. + }) => { + if is_excluded(&fragment.directives, executor.variables()) { + continue; + } + + let sub_exec = executor.type_sub_executor( + fragment.type_condition.as_ref().map(|c| c.item), + Some(&fragment.selection_set[..]), + ); + + if let Some(ref type_condition) = fragment.type_condition { + // FIXME: implement async version. + + let sub_result = instance.resolve_into_type( + info, + type_condition.item, + Some(&fragment.selection_set[..]), + &sub_exec, + ); + + if let Ok(Value::Object(obj)) = sub_result { + for (k, v) in obj { + merge_key_into(&mut object, &k, v); + } + } else if let Err(e) = sub_result { + sub_exec.push_error_at(e, start_pos.clone()); + } + } else { + let f = async move { + let value = resolve_selection_set_into_async( + instance, + info, + &fragment.selection_set[..], + &sub_exec, + ) + .await; + AsyncValue::Nested(value) + }; + async_values.push(f.boxed()); + } + } + } + } + + while let Some(item) = async_values.next().await { + match item { + AsyncValue::Field(AsyncField { name, value }) => { + if let Some(value) = value { + object.add_field(&name, value); + } else { + return Value::null(); + } + } + AsyncValue::Nested(obj) => match obj { + v @ Value::Null => { + return v; + } + Value::Object(obj) => { + for (k, v) in obj { + merge_key_into(&mut object, &k, v); + } + } + _ => unreachable!(), + }, + } + } + + Value::Object(object) +} diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index d17f47a9..4a2e2315 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -343,7 +343,7 @@ where } } -pub(crate) fn resolve_selection_set_into( +pub fn resolve_selection_set_into( instance: &T, info: &T::TypeInfo, selection_set: &[Selection], @@ -499,7 +499,10 @@ where true } -fn is_excluded(directives: &Option>>>, vars: &Variables) -> bool +pub(super) fn is_excluded( + directives: &Option>>>, + vars: &Variables, +) -> bool where S: ScalarValue, for<'b> &'b S: ScalarRefValue<'b>, @@ -528,7 +531,7 @@ where false } -fn merge_key_into(result: &mut Object, response_name: &str, value: Value) { +pub(crate) fn merge_key_into(result: &mut Object, response_name: &str, value: Value) { if let Some(&mut (_, ref mut e)) = result .iter_mut() .find(|&&mut (ref key, _)| key == response_name) diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 6a93e631..caa0e453 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -217,3 +217,106 @@ where Value::list(result) } + +#[cfg(feature = "async")] +async fn resolve_into_list_async<'a, S, T, I>( + executor: &'a Executor<'a, T::Context, S>, + info: &'a T::TypeInfo, + items: I, +) -> Value +where + S: ScalarValue + Send + Sync, + I: Iterator + ExactSizeIterator, + T: crate::GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + T::Context: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + use futures::stream::{FuturesOrdered, StreamExt}; + use std::iter::FromIterator; + + let stop_on_null = executor + .current_type() + .list_contents() + .expect("Current type is not a list type") + .is_non_null(); + + let iter = + items.map(|item| async move { executor.resolve_into_value_async(info, &item).await }); + let mut futures = FuturesOrdered::from_iter(iter); + + let mut values = Vec::with_capacity(futures.len()); + while let Some(value) = futures.next().await { + if stop_on_null && value.is_null() { + return value; + } + values.push(value); + } + + Value::list(values) +} + +#[cfg(feature = "async")] +impl crate::GraphQLTypeAsync for Vec +where + T: crate::GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, Value> { + let f = resolve_into_list_async(executor, info, self.iter()); + futures::future::FutureExt::boxed(f) + } +} + +#[cfg(feature = "async")] +impl crate::GraphQLTypeAsync for &[T] +where + T: crate::GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, Value> { + let f = resolve_into_list_async(executor, info, self.iter()); + futures::future::FutureExt::boxed(f) + } +} + +#[cfg(feature = "async")] +impl crate::GraphQLTypeAsync for Option +where + T: crate::GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + S: ScalarValue + Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, Value> { + let f = async move { + match *self { + Some(ref obj) => executor.resolve_into_value_async(info, obj).await, + None => Value::null(), + } + }; + futures::future::FutureExt::boxed(f) + } +} diff --git a/juniper/src/types/mod.rs b/juniper/src/types/mod.rs index 3780e184..a394161e 100644 --- a/juniper/src/types/mod.rs +++ b/juniper/src/types/mod.rs @@ -4,3 +4,6 @@ pub mod name; pub mod pointers; pub mod scalars; pub mod utilities; + +#[cfg(feature = "async")] +pub mod async_await; diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 12f905ba..118f9fcc 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -85,7 +85,7 @@ where } } -impl<'a, S, T, CtxT> GraphQLType for &'a T +impl<'e, S, T, CtxT> GraphQLType for &'e T where S: ScalarValue, T: GraphQLType, @@ -136,6 +136,35 @@ where } } +#[cfg(feature = "async")] +impl<'e, S, T> crate::GraphQLTypeAsync for &'e T +where + S: ScalarValue + Send + Sync, + T: crate::GraphQLTypeAsync, + T::TypeInfo: Send + Sync, + T::Context: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field_name: &'b str, + arguments: &'b Arguments, + executor: &'b Executor, + ) -> futures::future::BoxFuture<'b, ExecutionResult> { + crate::GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor) + } + + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, Value> { + crate::GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor) + } +} + impl<'a, T, S> ToInputValue for &'a T where S: Debug, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 8ed92ea4..dc30c39a 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -196,6 +196,23 @@ where } } +#[cfg(feature = "async")] +impl<'e, S> crate::GraphQLTypeAsync for &'e str +where + S: ScalarValue + Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> futures::future::BoxFuture<'a, crate::Value> { + use futures::future; + future::FutureExt::boxed(future::ready(self.resolve(info, selection_set, executor))) + } +} + impl<'a, S> ToInputValue for &'a str where S: ScalarValue, diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 3a61ccc7..6faa8904 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -120,6 +120,13 @@ where } } + pub fn into_object(self) -> Option> { + match self { + Value::Object(o) => Some(o), + _ => None, + } + } + /// Mutable view into the underlying object value, if present. pub fn as_mut_object_value(&mut self) -> Option<&mut Object> { match *self { diff --git a/juniper/src/value/object.rs b/juniper/src/value/object.rs index 36f8432e..1e8ee25d 100644 --- a/juniper/src/value/object.rs +++ b/juniper/src/value/object.rs @@ -76,6 +76,19 @@ impl Object { .find(|&&(ref k, _)| (k as &str) == key) .map(|&(_, ref value)| value) } + + pub fn sort_by_field(&mut self) { + self.key_value_list + .sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + for (_, ref mut value) in &mut self.key_value_list { + match value { + Value::Object(ref mut o) => { + o.sort_by_field(); + } + _ => {} + } + } + } } impl IntoIterator for Object { diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 78042591..d6a384ce 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -260,6 +260,8 @@ pub enum DefaultScalarValue { Boolean(bool), } +trait S: Send + Sync {} + impl ScalarValue for DefaultScalarValue { type Visitor = DefaultScalarValueVisitor; diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index fe4ed6ae..d8c83e1b 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -14,6 +14,9 @@ edition = "2018" [lib] proc-macro = true +[features] +async = [] + [dependencies] proc-macro2 = "1.0.1" syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] } diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index 6246d0ff..d1262e7d 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -206,9 +206,34 @@ pub fn impl_enum(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream { }); } + #[cfg(feature = "async")] + let _async = quote!( + impl<__S> #juniper_path::GraphQLTypeAsync<__S> for #ident + where + __S: #juniper_path::ScalarValue + Send + Sync, + for<'__b> &'__b __S: #juniper_path::ScalarRefValue<'__b> + { + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [#juniper_path::Selection<__S>]>, + executor: &'a #juniper_path::Executor, + ) -> futures::future::BoxFuture<'a, #juniper_path::Value<__S>> { + use #juniper_path::GraphQLType; + use futures::future; + let v = self.resolve(info, selection_set, executor); + future::FutureExt::boxed(future::ready(v)) + } + } + ); + + #[cfg(not(feature = "async"))] + let _async = quote!(); + let body = quote! { impl<__S> #juniper_path::GraphQLType<__S> for #ident - where __S: #juniper_path::ScalarValue, + where __S: + #juniper_path::ScalarValue, for<'__b> &'__b __S: #juniper_path::ScalarRefValue<'__b> { type Context = (); @@ -261,6 +286,8 @@ pub fn impl_enum(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream { } } } + + #_async }; body } diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs index f76b2505..c6042aa7 100644 --- a/juniper_codegen/src/derive_object.rs +++ b/juniper_codegen/src/derive_object.rs @@ -59,6 +59,7 @@ pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStr description: field_attrs.description, deprecation: field_attrs.deprecation, resolver_code, + resolver_code_async: None, }) } }); @@ -74,6 +75,7 @@ pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStr interfaces: None, include_type_generics: true, generic_scalar: true, + no_async: attrs.no_async, }; let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 12af7f41..992e3570 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -86,6 +86,7 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> }, include_type_generics: false, generic_scalar: false, + no_async: impl_attrs.no_async, }; for item in _impl.items { @@ -101,6 +102,8 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> } }; + let is_async = method.sig.asyncness.is_some(); + let attrs = match util::FieldAttributes::from_attrs( method.attrs, util::FieldAttributeParseMode::Impl, @@ -195,12 +198,28 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> let body = &method.block; let return_ty = &method.sig.output; - let resolver_code = quote!( - (|| #return_ty { - #( #resolve_parts )* - #body - })() - ); + + let (resolver_code, resolver_code_async) = if is_async { + ( + quote!(), + Some(quote!( + (async move || #return_ty { + #( #resolve_parts )* + #body + })() + )), + ) + } else { + ( + quote!( + (|| #return_ty { + #( #resolve_parts )* + #body + })() + ), + None, + ) + }; let ident = &method.sig.ident; let name = attrs @@ -214,6 +233,7 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> description: attrs.description, deprecation: attrs.deprecation, resolver_code, + resolver_code_async, }); } _ => { diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index d11f8788..3c3b7d6c 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -297,6 +297,7 @@ pub struct ObjectAttributes { pub context: Option, pub scalar: Option, pub interfaces: Vec, + pub no_async: bool, } impl syn::parse::Parse for ObjectAttributes { @@ -307,6 +308,7 @@ impl syn::parse::Parse for ObjectAttributes { context: None, scalar: None, interfaces: Vec::new(), + no_async: false, }; while !input.is_empty() { @@ -350,6 +352,10 @@ impl syn::parse::Parse for ObjectAttributes { .into_iter() .collect(); } + // FIXME: make this unneccessary. + "noasync" => { + output.no_async = true; + } other => { return Err(input.error(format!("Unknown attribute: {}", other))); } @@ -591,6 +597,14 @@ pub struct GraphQLTypeDefinitionField { pub deprecation: Option, pub args: Vec, pub resolver_code: proc_macro2::TokenStream, + pub resolver_code_async: Option, +} + +impl GraphQLTypeDefinitionField { + #[inline] + fn is_async(&self) -> bool { + self.resolver_code_async.is_some() + } } /// Definition of a graphql type based on information extracted @@ -618,9 +632,15 @@ pub struct GraphQLTypeDefiniton { // If false, the scalar is only generic if a generic parameter // is specified manually. pub generic_scalar: bool, + // FIXME: make this redundant. + pub no_async: bool, } impl GraphQLTypeDefiniton { + fn has_async_field(&self) -> bool { + self.fields.iter().any(|field| field.is_async()) + } + pub fn into_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream { let juniper_crate_name = syn::parse_str::(juniper_crate_name).unwrap(); @@ -691,21 +711,30 @@ impl GraphQLTypeDefiniton { let name = &field.name; let code = &field.resolver_code; - quote!( - #name => { - let res = { #code }; - #juniper_crate_name::IntoResolvable::into( - res, - executor.context() - ) - .and_then(|res| { - match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(#juniper_crate_name::Value::null()), - } - }) - }, - ) + if field.is_async() { + // TODO: better error message with field/type name. + quote!( + #name => { + panic!("Tried to resolve async field with a sync resolver"); + }, + ) + } else { + quote!( + #name => { + let res = { #code }; + #juniper_crate_name::IntoResolvable::into( + res, + executor.context() + ) + .and_then(|res| { + match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(#juniper_crate_name::Value::null()), + } + }) + }, + ) + } }); let description = self @@ -778,6 +807,114 @@ impl GraphQLTypeDefiniton { }; let (impl_generics, _, where_clause) = generics.split_for_impl(); + #[cfg(feature = "async")] + let resolve_field_async = { + let resolve_matches_async = self.fields.iter().map(|field| { + let name = &field.name; + + if let Some(code) = field.resolver_code_async.as_ref() { + quote!( + #name => { + let f = async move { + let res = { #code }.await; + + let inner_res = #juniper_crate_name::IntoResolvable::into( + res, + executor.context() + ); + match inner_res { + Ok(Some((ctx, r))) => { + let subexec = executor + .replaced_context(ctx); + subexec.resolve_with_ctx_async(&(), &r) + .await + }, + Ok(None) => Ok(#juniper_crate_name::Value::null()), + Err(e) => Err(e), + } + }; + future::FutureExt::boxed(f) + }, + ) + } else { + let code = &field.resolver_code; + + let inner = if !self.no_async { + quote!( + let f = async move { + match res2 { + Ok(Some((ctx, r))) => { + let sub = executor.replaced_context(ctx); + sub.resolve_with_ctx_async(&(), &r).await + }, + Ok(None) => Ok(#juniper_crate_name::Value::null()), + Err(e) => Err(e), + } + }; + future::FutureExt::boxed(f) + ) + } else { + quote!( + let v = match res2 { + Ok(Some((ctx, r))) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + Ok(None) => Ok(#juniper_crate_name::Value::null()), + Err(e) => Err(e), + }; + future::FutureExt::boxed(future::ready(v)) + ) + }; + + quote!( + #name => { + let res = { #code }; + let res2 = #juniper_crate_name::IntoResolvable::into( + res, + executor.context() + ); + #inner + }, + ) + } + }); + + let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));; + + where_async + .predicates + .push(parse_quote!( #scalar: Send + Sync )); + where_async.predicates.push(parse_quote!(Self: Send + Sync)); + + // FIXME: add where clause for interfaces. + + quote!( + impl#impl_generics #juniper_crate_name::GraphQLTypeAsync<#scalar> for #ty #type_generics_tokens + #where_async + { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b #juniper_crate_name::Arguments<#scalar>, + executor: &'b #juniper_crate_name::Executor, + ) -> futures::future::BoxFuture<'b, #juniper_crate_name::ExecutionResult<#scalar>> + where #scalar: Send + Sync, + { + use futures::future; + use #juniper_crate_name::GraphQLType; + match field { + #( #resolve_matches_async )* + _ => { + panic!("Field {} not found on type {}", field, "Mutation"); + } + } + } + } + ) + }; + + #[cfg(not(feature = "async"))] + let resolve_field_async = quote!(); + let output = quote!( impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens #where_clause @@ -822,11 +959,14 @@ impl GraphQLTypeDefiniton { } } + fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { #name.to_string() } - } + } + + #resolve_field_async ); output } diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 75b28bb9..0af899da 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -8,6 +8,9 @@ documentation = "https://docs.rs/juniper_warp" repository = "https://github.com/graphql-rust/juniper" edition = "2018" +[features] +async = [ "juniper/async", "futures03" ] + [dependencies] warp = "0.1.8" juniper = { version = "0.14.0", path = "../juniper", default-features = false } @@ -18,6 +21,8 @@ futures = "0.1.23" serde = "1.0.75" tokio-threadpool = "0.1.7" +futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview" } + [dev-dependencies] juniper = { version = "0.14.0", path = "../juniper", features = ["expose-test-schema", "serde_json"] } env_logger = "0.5.11" diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index a9f37d08..e0bda8c2 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -41,11 +41,12 @@ Check the LICENSE file for details. #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] use futures::{future::poll_fn, Future}; -use juniper::{DefaultScalarValue, InputValue, ScalarRefValue, ScalarValue}; use serde::Deserialize; use std::sync::Arc; use warp::{filters::BoxedFilter, Filter}; +use juniper::{DefaultScalarValue, InputValue, ScalarRefValue, ScalarValue}; + #[derive(Debug, serde_derive::Deserialize, PartialEq)] #[serde(untagged)] #[serde(bound = "InputValue: Deserialize<'de>")] @@ -240,6 +241,80 @@ where get_filter.or(post_filter).unify().boxed() } +/// FIXME: docs +pub fn make_graphql_filter_async( + schema: juniper::RootNode<'static, Query, Mutation, S>, + context_extractor: BoxedFilter<(Context,)>, +) -> BoxedFilter<(warp::http::Response>,)> +where + S: ScalarValue + Send + Sync + 'static, + for<'b> &'b S: ScalarRefValue<'b>, + Context: Send + Sync + 'static, + Query: juniper::GraphQLTypeAsync + Send + Sync + 'static, + Query::TypeInfo: Send + Sync, + Mutation: juniper::GraphQLTypeAsync + Send + Sync + 'static, + Mutation::TypeInfo: Send + Sync, +{ + let schema = Arc::new(schema); + let post_schema = schema.clone(); + + let handle_post_request = + move |context: Context, request: GraphQLBatchRequest| -> Response { + let schema = post_schema.clone(); + Box::new( + poll_fn(move || { + tokio_threadpool::blocking(|| { + let response = request.execute(&schema, &context); + Ok((serde_json::to_vec(&response)?, response.is_ok())) + }) + }) + .and_then(|result| ::futures::future::done(Ok(build_response(result)))) + .map_err(|e: tokio_threadpool::BlockingError| warp::reject::custom(e)), + ) + }; + + let post_filter = warp::post2() + .and(context_extractor.clone()) + .and(warp::body::json()) + .and_then(handle_post_request); + + let handle_get_request = move |context: Context, + mut request: std::collections::HashMap| + -> Response { + let schema = schema.clone(); + Box::new( + poll_fn(move || { + tokio_threadpool::blocking(|| { + let variables = match request.remove("variables") { + None => None, + Some(vs) => serde_json::from_str(&vs)?, + }; + + let graphql_request = juniper::http::GraphQLRequest::new( + request.remove("query").ok_or_else(|| { + failure::format_err!("Missing GraphQL query string in query parameters") + })?, + request.get("operation_name").map(|s| s.to_owned()), + variables, + ); + + let response = graphql_request.execute(&schema, &context); + Ok((serde_json::to_vec(&response)?, response.is_ok())) + }) + }) + .and_then(|result| ::futures::future::done(Ok(build_response(result)))) + .map_err(|e: tokio_threadpool::BlockingError| warp::reject::custom(e)), + ) + }; + + let get_filter = warp::get2() + .and(context_extractor.clone()) + .and(warp::filters::query::query()) + .and_then(handle_get_request); + + get_filter.or(post_filter).unify().boxed() +} + fn build_response( response: Result<(Vec, bool), failure::Error>, ) -> warp::http::Response> { From 92871a9253671978b33864c29c1a3b8bea1871e4 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 19 Aug 2019 22:17:05 +0200 Subject: [PATCH 13/25] Update warp for async --- juniper/src/http/mod.rs | 27 +++++++++++++++++ juniper/src/types/scalars.rs | 12 ++++++++ juniper_warp/Cargo.toml | 2 +- juniper_warp/src/lib.rs | 58 +++++++++++++++++++++++++++++------- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs index ab3c2588..e6fbf21e 100644 --- a/juniper/src/http/mod.rs +++ b/juniper/src/http/mod.rs @@ -93,6 +93,33 @@ where context, )) } + + #[cfg(feature = "async")] + pub async fn execute_async<'a, CtxT, QueryT, MutationT>( + &'a self, + root_node: &'a RootNode<'a, QueryT, MutationT, S>, + context: &'a CtxT, + ) -> GraphQLResponse<'a, S> + where + S: ScalarValue + Send + Sync, + QueryT: crate::GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: crate::GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, + { + let op = self.operation_name(); + let vars = &self.variables(); + let res = crate::execute_async( + &self.query, + op, + root_node, + vars, + context, + ).await; + GraphQLResponse(res) + } } /// Simple wrapper around the result from executing a GraphQL query diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index dc30c39a..a2df7eb3 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -329,6 +329,18 @@ where } } +#[cfg(feature = "async")] +impl crate::GraphQLTypeAsync for EmptyMutation +where + S: ScalarValue + Send + Sync, + Self: GraphQLType + Send + Sync, + Self::TypeInfo: Send + Sync, + Self::Context: Send + Sync, + T: Send + Sync, + for<'b> &'b S: ScalarRefValue<'b>, +{ +} + #[cfg(test)] mod tests { use super::ID; diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 0af899da..fc1eed48 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.1.23" serde = "1.0.75" tokio-threadpool = "0.1.7" -futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview" } +futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview", features = ["compat"] } [dev-dependencies] juniper = { version = "0.14.0", path = "../juniper", features = ["expose-test-schema", "serde_json"] } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index e0bda8c2..6f6b7711 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -40,11 +40,16 @@ Check the LICENSE file for details. #![deny(warnings)] #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] +#![cfg_attr(feature = "async", feature(async_await, async_closure))] + use futures::{future::poll_fn, Future}; use serde::Deserialize; use std::sync::Arc; use warp::{filters::BoxedFilter, Filter}; +#[cfg(feature = "async")] +use futures03::future::{FutureExt, TryFutureExt}; + use juniper::{DefaultScalarValue, InputValue, ScalarRefValue, ScalarValue}; #[derive(Debug, serde_derive::Deserialize, PartialEq)] @@ -84,6 +89,37 @@ where ), } } + + #[cfg(feature = "async")] + pub async fn execute_async<'a, CtxT, QueryT, MutationT>( + &'a self, + root_node: &'a juniper::RootNode<'a, QueryT, MutationT, S>, + context: &'a CtxT, + ) -> GraphQLBatchResponse<'a, S> + where + QueryT: juniper::GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: juniper::GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, + S: Send + Sync, + { + match self { + &GraphQLBatchRequest::Single(ref request) => { + let res = request.execute_async(root_node, context).await; + GraphQLBatchResponse::Single(res) + } + &GraphQLBatchRequest::Batch(ref requests) => { + let futures = requests + .iter() + .map(|request| request.execute_async(root_node, context)) + .collect::>(); + let responses = futures03::future::join_all(futures).await; + + GraphQLBatchResponse::Batch(responses) + } + } + } } #[derive(serde_derive::Serialize)] @@ -242,6 +278,7 @@ where } /// FIXME: docs +#[cfg(feature = "async")] pub fn make_graphql_filter_async( schema: juniper::RootNode<'static, Query, Mutation, S>, context_extractor: BoxedFilter<(Context,)>, @@ -261,16 +298,17 @@ where let handle_post_request = move |context: Context, request: GraphQLBatchRequest| -> Response { let schema = post_schema.clone(); - Box::new( - poll_fn(move || { - tokio_threadpool::blocking(|| { - let response = request.execute(&schema, &context); - Ok((serde_json::to_vec(&response)?, response.is_ok())) - }) - }) - .and_then(|result| ::futures::future::done(Ok(build_response(result)))) - .map_err(|e: tokio_threadpool::BlockingError| warp::reject::custom(e)), - ) + + let f = async move { + let res = request.execute_async(&schema, &context).await; + + match serde_json::to_vec(&res) { + Ok(json) => Ok(build_response(Ok((json, res.is_ok())))), + Err(e) => Err(warp::reject::custom(e)), + } + }; + + Box::new(f.boxed().compat()) }; let post_filter = warp::post2() From 57ecd4b4716275262a58a663cb2c1e9b7caffddd Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 19 Aug 2019 22:40:22 +0200 Subject: [PATCH 14/25] Add warp_async example --- Cargo.toml | 1 + examples/warp_async/.gitignore | 1 + examples/warp_async/Cargo.toml | 17 ++++++ examples/warp_async/src/main.rs | 99 +++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 examples/warp_async/.gitignore create mode 100644 examples/warp_async/Cargo.toml create mode 100644 examples/warp_async/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 35fbad11..6f61e9f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ members = [ ] exclude = [ "docs/book/tests", + "examples/warp_async", ] diff --git a/examples/warp_async/.gitignore b/examples/warp_async/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/examples/warp_async/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml new file mode 100644 index 00000000..1891ad97 --- /dev/null +++ b/examples/warp_async/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "warp_async" +version = "0.1.0" +authors = ["Christoph Herzog "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } +juniper_warp = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } +log = "0.4.8" +env_logger = "0.6.2" +warp = "0.1.19" +futures-preview = { version = "0.3.0-alpha.18", features = ["nightly", "async-await", "compat"] } +reqwest = "0.9.19" + diff --git a/examples/warp_async/src/main.rs b/examples/warp_async/src/main.rs new file mode 100644 index 00000000..70112bae --- /dev/null +++ b/examples/warp_async/src/main.rs @@ -0,0 +1,99 @@ +//! +//! This example demonstrates async/await usage with warp. +//! NOTE: this uses tokio 0.1 , not the alpha tokio 0.2. + +#![feature(async_await, async_closure)] + +use juniper::{EmptyMutation, RootNode, FieldError}; +use warp::{http::Response, Filter}; + +#[derive(Clone)] +struct Context { + +} +impl juniper::Context for Context {} + +struct User { + id: i32, + name: String, +} + +#[juniper::object(Context = Context)] +impl User { + fn id(&self) -> i32 { + self.id + } + + fn name(&self) -> &str { + &self.name + } + + async fn friends(&self) -> Vec { + vec![] + } +} + +struct Query; + +#[juniper::object(Context = Context)] +impl Query { + async fn users() -> Vec { + vec![ + User{ + id: 1, + name: "user1".into(), + }, + ] + } + + /// Fetch a URL and return the response body text. + async fn request(url: String) -> Result { + use futures::{ compat::{Stream01CompatExt, Future01CompatExt}, stream::TryStreamExt}; + + let res = reqwest::r#async::Client::new() + .get(&url) + .send() + .compat() + .await?; + + let body_raw = res.into_body().compat().try_concat().await?; + let body = std::str::from_utf8(&body_raw).unwrap_or("invalid utf8"); + Ok(body.to_string()) + } +} + +type Schema = RootNode<'static, Query, EmptyMutation>; + +fn schema() -> Schema { + Schema::new(Query, EmptyMutation::::new()) +} + +fn main() { + ::std::env::set_var("RUST_LOG", "warp_async"); + env_logger::init(); + + let log = warp::log("warp_server"); + + let homepage = warp::path::end().map(|| { + Response::builder() + .header("content-type", "text/html") + .body(format!( + "

juniper_warp

visit /graphiql" + )) + }); + + log::info!("Listening on 127.0.0.1:8080"); + + let state = warp::any().map(move || Context{} ); + let graphql_filter = juniper_warp::make_graphql_filter_async(schema(), state.boxed()); + + warp::serve( + warp::get2() + .and(warp::path("graphiql")) + .and(juniper_warp::graphiql_filter("/graphql")) + .or(homepage) + .or(warp::path("graphql").and(graphql_filter)) + .with(log), + ) + .run(([127, 0, 0, 1], 8080)); +} From 3d9fc8e34715305b4436ef28b9fadb9c3539adc9 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Mon, 19 Aug 2019 23:16:50 +0200 Subject: [PATCH 15/25] examples/warp_async: update dependency paths --- examples/warp_async/Cargo.toml | 6 ++++-- examples/warp_async/src/main.rs | 13 +++++++++++++ juniper/src/lib.rs | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml index 1891ad97..c33445e8 100644 --- a/examples/warp_async/Cargo.toml +++ b/examples/warp_async/Cargo.toml @@ -7,11 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } -juniper_warp = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } log = "0.4.8" env_logger = "0.6.2" warp = "0.1.19" futures-preview = { version = "0.3.0-alpha.18", features = ["nightly", "async-await", "compat"] } reqwest = "0.9.19" +juniper_codegen = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } +juniper = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } +juniper_warp = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } + diff --git a/examples/warp_async/src/main.rs b/examples/warp_async/src/main.rs index 70112bae..170d743b 100644 --- a/examples/warp_async/src/main.rs +++ b/examples/warp_async/src/main.rs @@ -13,8 +13,16 @@ struct Context { } impl juniper::Context for Context {} +#[derive(juniper::GraphQLEnum, Clone, Copy)] +enum UserKind { + Admin, + User, + Guest, +} + struct User { id: i32, + kind: UserKind, name: String, } @@ -24,6 +32,10 @@ impl User { self.id } + fn kind(&self) -> UserKind { + self.kind + } + fn name(&self) -> &str { &self.name } @@ -41,6 +53,7 @@ impl Query { vec![ User{ id: 1, + kind: UserKind::Admin, name: "user1".into(), }, ] diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 95ce987b..a6671054 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -90,6 +90,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. */ #![doc(html_root_url = "https://docs.rs/juniper/0.14.0")] #![warn(missing_docs)] + #![cfg_attr(feature = "async", feature(async_await, async_closure))] #[doc(hidden)] From c113ef617189a7691aedb70a4d7d925f2e6a03da Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Wed, 21 Aug 2019 12:07:30 +0200 Subject: [PATCH 16/25] Experimental benchmarks --- Cargo.toml | 1 + benches/bench.rs | 127 ------------------------ juniper/src/http/mod.rs | 8 +- juniper/src/lib.rs | 1 - juniper_benchmarks/.gitignore | 3 + juniper_benchmarks/Cargo.toml | 19 ++++ juniper_benchmarks/benches/benchmark.rs | 74 ++++++++++++++ juniper_benchmarks/src/lib.rs | 116 ++++++++++++++++++++++ juniper_warp/src/lib.rs | 3 +- 9 files changed, 215 insertions(+), 137 deletions(-) delete mode 100644 benches/bench.rs create mode 100644 juniper_benchmarks/.gitignore create mode 100644 juniper_benchmarks/Cargo.toml create mode 100644 juniper_benchmarks/benches/benchmark.rs create mode 100644 juniper_benchmarks/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 6f61e9f8..8a2553e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] # Order is important as this is the order the crates will be released. members = [ + "juniper_benchmarks", "juniper_codegen", "juniper", "integration_tests/juniper_tests", diff --git a/benches/bench.rs b/benches/bench.rs deleted file mode 100644 index 5d707527..00000000 --- a/benches/bench.rs +++ /dev/null @@ -1,127 +0,0 @@ -#[macro_use] extern crate bencher; -extern crate juniper; - -use bencher::Bencher; - -use juniper::{execute, RootNode, EmptyMutation, Variables}; -use juniper::tests::model::Database; - -fn query_type_name(b: &mut Bencher) { - let database = Database::new(); - let schema = RootNode::new(&database, EmptyMutation::::new()); - - let doc = r#" - query IntrospectionQueryTypeQuery { - __schema { - queryType { - name - } - } - }"#; - - b.iter(|| execute(doc, None, &schema, &Variables::new(), &database)); -} - -fn introspection_query(b: &mut Bencher) { - let database = Database::new(); - let schema = RootNode::new(&database, EmptyMutation::::new()); - - let doc = r#" - query IntrospectionQuery { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { - ...FullType - } - directives { - name - description - locations - args { - ...InputValue - } - } - } - } - - fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - possibleTypes { - ...TypeRef - } - } - - fragment InputValue on __InputValue { - name - description - type { ...TypeRef } - defaultValue - } - - fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } - } -"#; - - b.iter(|| execute(doc, None, &schema, &Variables::new(), &database)); -} - -benchmark_group!(queries, query_type_name, introspection_query); -benchmark_main!(queries); diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs index e6fbf21e..2d4e62bd 100644 --- a/juniper/src/http/mod.rs +++ b/juniper/src/http/mod.rs @@ -111,13 +111,7 @@ where { let op = self.operation_name(); let vars = &self.variables(); - let res = crate::execute_async( - &self.query, - op, - root_node, - vars, - context, - ).await; + let res = crate::execute_async(&self.query, op, root_node, vars, context).await; GraphQLResponse(res) } } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index a6671054..95ce987b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -90,7 +90,6 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. */ #![doc(html_root_url = "https://docs.rs/juniper/0.14.0")] #![warn(missing_docs)] - #![cfg_attr(feature = "async", feature(async_await, async_closure))] #[doc(hidden)] diff --git a/juniper_benchmarks/.gitignore b/juniper_benchmarks/.gitignore new file mode 100644 index 00000000..69369904 --- /dev/null +++ b/juniper_benchmarks/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/juniper_benchmarks/Cargo.toml b/juniper_benchmarks/Cargo.toml new file mode 100644 index 00000000..e4da5478 --- /dev/null +++ b/juniper_benchmarks/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "juniper_benchmarks" +version = "0.1.0" +authors = ["Christoph Herzog "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bench]] +name = "benchmark" +harness = false + +[dependencies] +juniper = { path = "../juniper", features = ["async"] } +futures-preview = "0.3.0-alpha.18" + +[dev-dependencies] +criterion = "0.2.11" +tokio = "0.2.0-alpha.2" diff --git a/juniper_benchmarks/benches/benchmark.rs b/juniper_benchmarks/benches/benchmark.rs new file mode 100644 index 00000000..fb0646ac --- /dev/null +++ b/juniper_benchmarks/benches/benchmark.rs @@ -0,0 +1,74 @@ +extern crate juniper_benchmarks; + +use criterion::{black_box, criterion_group, criterion_main, Criterion, ParameterizedBenchmark}; + +use juniper::{graphql_value, InputValue, ToInputValue, Value}; +use juniper_benchmarks as j; + +fn bench_sync_vs_async_single_user_flat_instant(c: &mut Criterion) { + const QUERY: &'static str = r#" + query Query($id: Int) { + user(id: $id) { + id + kind + username + email + } + } + "#; + + c.bench( + "Sync vs Async - Single User Flat - Instant", + ParameterizedBenchmark::new( + "Sync", + |b, count| { + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + b.iter(|| { + j::execute( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ) + }) + }, + vec![1, 10], + ) + .with_function("Async - Single Thread", |b, count| { + let mut rt = tokio::runtime::current_thread::Runtime::new().unwrap(); + + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute_async( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }) + .with_function("Async - Threadpool", |b, count| { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute_async( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }), + ); +} + +criterion_group!(benches, bench_sync_vs_async_single_user_flat_instant); +criterion_main!(benches); diff --git a/juniper_benchmarks/src/lib.rs b/juniper_benchmarks/src/lib.rs new file mode 100644 index 00000000..a9f2aa4b --- /dev/null +++ b/juniper_benchmarks/src/lib.rs @@ -0,0 +1,116 @@ +#![feature(async_await, async_closure)] + +use juniper::{ + object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables, +}; + +pub type QueryResult = Result< + ( + Value, + Vec>, + ), + String, +>; + +pub struct Context {} + +impl Context { + fn new() -> Self { + Self {} + } +} + +impl juniper::Context for Context {} + +#[derive(GraphQLEnum)] +pub enum Gender { + Male, + Female, + Other, +} + +#[derive(GraphQLEnum)] +pub enum UserKind { + SuperAdmin, + Admin, + Moderator, + User, + Guest, +} + +pub struct User { + pub id: i32, + pub kind: UserKind, + pub username: String, + pub email: String, + pub gender: Option, +} + +impl User { + fn new(id: i32) -> Self { + Self { + id, + kind: UserKind::Admin, + username: "userx".to_string(), + email: "userx@domain.com".to_string(), + gender: Some(Gender::Female), + } + } +} + +#[object(Context = Context)] +impl User {} + +pub struct Query; + +#[object(Context = Context)] +impl Query { + fn user_sync_instant(id: i32) -> Result { + Ok(User::new(id)) + } + + fn users_sync_instant(ids: Option>) -> Result, FieldError> { + if let Some(ids) = ids { + let users = ids.into_iter().map(User::new).collect(); + Ok(users) + } else { + Ok(vec![]) + } + } + + async fn user_async_instant(id: i32) -> Result { + Ok(User::new(id)) + } + + async fn users_async_instant(ids: Option>) -> Result, FieldError> { + if let Some(ids) = ids { + let users = ids.into_iter().map(User::new).collect(); + Ok(users) + } else { + Ok(vec![]) + } + } +} + +pub struct Mutation; + +#[object(Context = Context)] +impl Mutation {} + +pub fn new_schema() -> juniper::RootNode<'static, Query, Mutation> { + juniper::RootNode::new(Query, Mutation) +} + +pub fn execute(query: &str, vars: Variables) -> QueryResult { + let root = new_schema(); + let ctx = Context::new(); + juniper::execute(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e)) +} + +pub async fn execute_async(query: &str, vars: Variables) -> QueryResult { + let root = new_schema(); + let ctx = Context::new(); + juniper::execute_async(query, None, &root, &vars, &ctx) + .await + .map_err(|e| format!("{:?}", e)) +} diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 6f6b7711..a8725b29 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -39,7 +39,6 @@ Check the LICENSE file for details. #![deny(missing_docs)] #![deny(warnings)] #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] - #![cfg_attr(feature = "async", feature(async_await, async_closure))] use futures::{future::poll_fn, Future}; @@ -114,7 +113,7 @@ where .iter() .map(|request| request.execute_async(root_node, context)) .collect::>(); - let responses = futures03::future::join_all(futures).await; + let responses = futures03::future::join_all(futures).await; GraphQLBatchResponse::Batch(responses) } From 4a4d7407aaa7beca955c8863cda704e03d9f5336 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Wed, 21 Aug 2019 15:20:01 +0200 Subject: [PATCH 17/25] Use Custom BoxFuture type + prepare new resolve result value --- juniper/src/executor/mod.rs | 14 ++++++++++++++ juniper/src/lib.rs | 5 ++++- juniper/src/schema/schema.rs | 2 +- juniper/src/types/async_await.rs | 9 +++------ juniper/src/types/containers.rs | 6 +++--- juniper/src/types/pointers.rs | 4 ++-- juniper/src/types/scalars.rs | 2 +- juniper/src/value/mod.rs | 3 +++ juniper/src/value/object.rs | 1 + 9 files changed, 32 insertions(+), 14 deletions(-) diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 8b96b9ac..937376fc 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -210,6 +210,20 @@ impl FieldError { /// The result of resolving the value of a field of type `T` pub type FieldResult = Result>; +/* +pub enum ResolvedValue<'a, S = DefaultScalarValue> { + Value(Value), + Future(crate::BoxFuture<'a, Value>), +} + +impl<'a, S> From> for ResolvedValue<'a, S> { + #[inline] + fn from(value: Value) -> Self { + ResolvedValue::Value(value) + } +} +*/ + /// The result of resolving an unspecified field pub type ExecutionResult = Result, FieldError>; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 95ce987b..6317f4c6 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -176,8 +176,11 @@ pub use crate::{ }, }; +/// A pinned, boxed future that can be polled. +pub type BoxFuture<'a, T> = std::pin::Pin + 'a + Send>>; + #[cfg(feature = "async")] -pub use crate::types::async_await::GraphQLTypeAsync; +pub use crate::types::async_await::{GraphQLTypeAsync}; /// An error that prevented query execution #[derive(Debug, PartialEq)] diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 49b6940d..f71e9b18 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -94,7 +94,7 @@ where field_name: &'b str, arguments: &'b Arguments, executor: &'b Executor, - ) -> futures::future::BoxFuture<'b, ExecutionResult> { + ) -> crate::BoxFuture<'b, ExecutionResult> { use futures::future::{ready, FutureExt}; match field_name { "__schema" | "__type" => { diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 05fb502c..12369bef 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -1,4 +1,3 @@ -use futures::future::BoxFuture; use crate::ast::{Directive, FromInputValue, InputValue, Selection}; use crate::value::{Object, ScalarRefValue, ScalarValue, Value}; @@ -21,7 +20,7 @@ where field_name: &'a str, arguments: &'a Arguments, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, ExecutionResult> { + ) -> BoxFuture<'a, ExecutionResult> { panic!("resolve_field must be implemented by object types"); } @@ -30,7 +29,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, Value> { + ) -> BoxFuture<'a, Value> { if let Some(selection_set) = selection_set { resolve_selection_set_into_async(self, info, selection_set, executor) } else { @@ -47,7 +46,7 @@ pub(crate) fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>( info: &'a T::TypeInfo, selection_set: &'e [Selection<'e, S>], executor: &'e Executor<'e, CtxT, S>, -) -> futures::future::BoxFuture<'a, Value> +) -> BoxFuture<'a, Value> where T: GraphQLTypeAsync, T::TypeInfo: Send + Sync, @@ -71,8 +70,6 @@ enum AsyncValue { Nested(Value), } -// type ResolveFuture<'a, S> = BoxFuture<'a, AsyncResolve>; - #[cfg(feature = "async")] pub(crate) async fn resolve_selection_set_into_async_recursive<'a, T, CtxT, S>( instance: &'a T, diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index caa0e453..8004dbaf 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -270,7 +270,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, Value> { + ) -> crate::BoxFuture<'a, Value> { let f = resolve_into_list_async(executor, info, self.iter()); futures::future::FutureExt::boxed(f) } @@ -290,7 +290,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, Value> { + ) -> crate::BoxFuture<'a, Value> { let f = resolve_into_list_async(executor, info, self.iter()); futures::future::FutureExt::boxed(f) } @@ -310,7 +310,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, Value> { + ) -> crate::BoxFuture<'a, Value> { let f = async move { match *self { Some(ref obj) => executor.resolve_into_value_async(info, obj).await, diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 118f9fcc..1e8478a1 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -151,7 +151,7 @@ where field_name: &'b str, arguments: &'b Arguments, executor: &'b Executor, - ) -> futures::future::BoxFuture<'b, ExecutionResult> { + ) -> crate::BoxFuture<'b, ExecutionResult> { crate::GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor) } @@ -160,7 +160,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, Value> { + ) -> crate::BoxFuture<'a, Value> { crate::GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index a2df7eb3..dba13834 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -207,7 +207,7 @@ where info: &'a Self::TypeInfo, selection_set: Option<&'a [Selection]>, executor: &'a Executor, - ) -> futures::future::BoxFuture<'a, crate::Value> { + ) -> crate::BoxFuture<'a, crate::Value> { use futures::future; future::FutureExt::boxed(future::ready(self.resolve(info, selection_set, executor))) } diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 6faa8904..dc84256d 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -120,6 +120,9 @@ where } } + /// Convert this value into an Object. + /// + /// Returns None if value is not an Object. pub fn into_object(self) -> Option> { match self { Value::Object(o) => Some(o), diff --git a/juniper/src/value/object.rs b/juniper/src/value/object.rs index 1e8ee25d..72cea251 100644 --- a/juniper/src/value/object.rs +++ b/juniper/src/value/object.rs @@ -77,6 +77,7 @@ impl Object { .map(|&(_, ref value)| value) } + /// Recursively sort all keys by field. pub fn sort_by_field(&mut self) { self.key_value_list .sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); From 8033deba12c1e5fb1f38bd8667236caeae26dd27 Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Thu, 15 Aug 2019 16:06:31 +0100 Subject: [PATCH 18/25] Update to support async branch of rocket. --- juniper_rocket/Cargo.toml | 3 ++- juniper_rocket/src/lib.rs | 47 +++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index aa3605a1..bc1e8686 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -17,7 +17,8 @@ serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } juniper = { version = "0.14.0", default-features = false, path = "../juniper"} -rocket = { version = "0.4.0" } +futures-preview = { version = "0.3.0-alpha.18", features = ["compat"] } +rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } [dev-dependencies.juniper] version = "0.14.0" diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index a016f132..a3760aa3 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -37,7 +37,7 @@ Check the LICENSE file for details. */ #![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")] -#![feature(decl_macro, proc_macro_hygiene)] +#![feature(decl_macro, proc_macro_hygiene, async_await)] use std::{ error::Error, @@ -45,10 +45,10 @@ use std::{ }; use rocket::{ - data::{FromDataSimple, Outcome as FromDataOutcome}, + data::{FromDataFuture, FromDataSimple}, http::{ContentType, RawStr, Status}, request::{FormItems, FromForm, FromFormValue}, - response::{content, Responder, Response}, + response::{content, Responder, Response, ResultFuture}, Data, Outcome::{Failure, Forward, Success}, Request, @@ -331,38 +331,47 @@ where } } +const BODY_LIMIT: u64 = 1024 * 100; + impl FromDataSimple for GraphQLRequest where S: ScalarValue, { type Error = String; - fn from_data(request: &Request, data: Data) -> FromDataOutcome { + fn from_data(request: &Request, data: Data) -> FromDataFuture<'static, Self, Self::Error> { + use futures::io::AsyncReadExt; + use rocket::AsyncReadExt as _; if !request.content_type().map_or(false, |ct| ct.is_json()) { - return Forward(data); + return Box::pin(async move { Forward(data) }); } - let mut body = String::new(); - if let Err(e) = data.open().read_to_string(&mut body) { - return Failure((Status::InternalServerError, format!("{:?}", e))); - } + Box::pin(async move { + let mut body = String::new(); + let mut reader = data.open().take(BODY_LIMIT); + if let Err(e) = reader.read_to_string(&mut body).await { + return Failure((Status::InternalServerError, format!("{:?}", e))); + } - match serde_json::from_str(&body) { - Ok(value) => Success(GraphQLRequest(value)), - Err(failure) => return Failure((Status::BadRequest, format!("{}", failure))), - } + match serde_json::from_str(&body) { + Ok(value) => Success(GraphQLRequest(value)), + Err(failure) => Failure((Status::BadRequest, format!("{}", failure))), + } + }) } } impl<'r> Responder<'r> for GraphQLResponse { - fn respond_to(self, _: &Request) -> Result, Status> { + fn respond_to(self, _: &Request) -> ResultFuture<'r> { let GraphQLResponse(status, body) = self; - Ok(Response::build() - .header(ContentType::new("application", "json")) - .status(status) - .sized_body(Cursor::new(body)) - .finalize()) + Box::pin(async move { + Ok(Response::build() + .header(ContentType::new("application", "json")) + .status(status) + .sized_body(Cursor::new(body)) + .finalize()) + }) } } From 65a641c7d6c7246b8a8f188cc078c032884e1a0c Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Wed, 21 Aug 2019 10:22:16 +0100 Subject: [PATCH 19/25] Some more updates to juniper_rocket --- juniper_rocket/Cargo.toml | 5 +++- juniper_rocket/src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index bc1e8686..e1bc0564 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -11,13 +11,16 @@ documentation = "https://docs.rs/juniper_rocket" repository = "https://github.com/graphql-rust/juniper" edition = "2018" +[features] +async = [ "juniper/async", "futures03" ] + [dependencies] serde = { version = "1.0.2" } serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } juniper = { version = "0.14.0", default-features = false, path = "../juniper"} -futures-preview = { version = "0.3.0-alpha.18", features = ["compat"] } +futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview", features = ["compat"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } [dev-dependencies.juniper] diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index a3760aa3..c50f747d 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -37,7 +37,9 @@ Check the LICENSE file for details. */ #![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")] -#![feature(decl_macro, proc_macro_hygiene, async_await)] +#![feature(decl_macro, proc_macro_hygiene)] + +#![cfg_attr(feature = "async", feature(async_await, async_closure))] use std::{ error::Error, @@ -61,6 +63,9 @@ use juniper::{ ScalarValue, }; +#[cfg(feature = "async")] +use futures03::future::{FutureExt, TryFutureExt}; + #[derive(Debug, serde_derive::Deserialize, PartialEq)] #[serde(untagged)] #[serde(bound = "InputValue: Deserialize<'de>")] @@ -109,6 +114,31 @@ where } } + #[cfg(feature = "async")] + pub async fn execute_async<'a, CtxT, QueryT, MutationT>( + &'a self, + root_node: &'a RootNode, + context: &CtxT, + ) -> GraphQLBatchResponse<'a, S> + where + QueryT: GraphQLType, + MutationT: GraphQLType, + { + match self { + &GraphQLBatchRequest::Single(ref request) => { + GraphQLBatchResponse::Single(request.execute_async(root_node, context).await) + } + &GraphQLBatchRequest::Batch(ref requests) => GraphQLBatchResponse::Batch( + let futures = requests + .iter() + .map(|request| request.execute(root_node, context)) + .collect::>(), + + let responses = futures03::future::join_all(futures).await; + ), + } + } + pub fn operation_names(&self) -> Vec> { match self { GraphQLBatchRequest::Single(req) => vec![req.operation_name()], @@ -184,6 +214,28 @@ where GraphQLResponse(status, json) } + /// Asynchronously execute an incoming GraphQL query + #[cfg(feature = "async")] + pub async fn execute_async( + &self, + root_node: &RootNode, + context: &CtxT, + ) -> GraphQLResponse + where + QueryT: GraphQLType, + MutationT: GraphQLType, + { + let response = self.0.execute_async(root_node, context).await; + let status = if response.is_ok() { + Status::Ok + } else { + Status::BadRequest + }; + let json = serde_json::to_string(&response).unwrap(); + + GraphQLResponse(status, json) + } + /// Returns the operation names associated with this request. /// /// For batch requests there will be multiple names. @@ -340,7 +392,7 @@ where type Error = String; fn from_data(request: &Request, data: Data) -> FromDataFuture<'static, Self, Self::Error> { - use futures::io::AsyncReadExt; + use futures03::io::AsyncReadExt; use rocket::AsyncReadExt as _; if !request.content_type().map_or(false, |ct| ct.is_json()) { return Box::pin(async move { Forward(data) }); From 1e348ea1bdc1c7c42f7ae0553c3ae5aa56292aba Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Wed, 21 Aug 2019 12:40:43 +0100 Subject: [PATCH 20/25] Update for latest rust nightly --- juniper_rocket/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index c50f747d..9ebe0bbf 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -117,7 +117,7 @@ where #[cfg(feature = "async")] pub async fn execute_async<'a, CtxT, QueryT, MutationT>( &'a self, - root_node: &'a RootNode, + root_node: &'a RootNode<'_, QueryT, MutationT, S>, context: &CtxT, ) -> GraphQLBatchResponse<'a, S> where @@ -128,14 +128,14 @@ where &GraphQLBatchRequest::Single(ref request) => { GraphQLBatchResponse::Single(request.execute_async(root_node, context).await) } - &GraphQLBatchRequest::Batch(ref requests) => GraphQLBatchResponse::Batch( + &GraphQLBatchRequest::Batch(ref requests) => { let futures = requests .iter() - .map(|request| request.execute(root_node, context)) - .collect::>(), + .map(|request| request.execute_async(root_node, context)) + .collect::>(); - let responses = futures03::future::join_all(futures).await; - ), + GraphQLBatchResponse::Batch(futures03::future::join_all(futures).await) + }, } } @@ -218,7 +218,7 @@ where #[cfg(feature = "async")] pub async fn execute_async( &self, - root_node: &RootNode, + root_node: &RootNode<'_, QueryT, MutationT, S>, context: &CtxT, ) -> GraphQLResponse where From 47e7003a7b9e6227b97059be4689ad26cb1f442e Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Wed, 21 Aug 2019 13:16:19 +0100 Subject: [PATCH 21/25] Fix the rest of the rocket stuff --- juniper_rocket/Cargo.toml | 4 ++-- juniper_rocket/src/lib.rs | 42 +++++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index e1bc0564..1d8feda5 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/graphql-rust/juniper" edition = "2018" [features] -async = [ "juniper/async", "futures03" ] +async = [ "juniper/async" ] [dependencies] serde = { version = "1.0.2" } @@ -20,7 +20,7 @@ serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } juniper = { version = "0.14.0", default-features = false, path = "../juniper"} -futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview", features = ["compat"] } +futures03 = { version = "0.3.0-alpha.18", package = "futures-preview", features = ["compat"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } [dev-dependencies.juniper] diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 9ebe0bbf..891a3723 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -38,12 +38,11 @@ Check the LICENSE file for details. #![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")] #![feature(decl_macro, proc_macro_hygiene)] - #![cfg_attr(feature = "async", feature(async_await, async_closure))] use std::{ error::Error, - io::{Cursor, Read}, + io::Cursor, }; use rocket::{ @@ -63,6 +62,9 @@ use juniper::{ ScalarValue, }; +#[cfg(feature = "async")] +use juniper::GraphQLTypeAsync; + #[cfg(feature = "async")] use futures03::future::{FutureExt, TryFutureExt}; @@ -71,7 +73,7 @@ use futures03::future::{FutureExt, TryFutureExt}; #[serde(bound = "InputValue: Deserialize<'de>")] enum GraphQLBatchRequest where - S: ScalarValue, + S: ScalarValue + Sync + Send, { Single(http::GraphQLRequest), Batch(Vec>), @@ -81,7 +83,7 @@ where #[serde(untagged)] enum GraphQLBatchResponse<'a, S = DefaultScalarValue> where - S: ScalarValue, + S: ScalarValue + Sync + Send, { Single(http::GraphQLResponse<'a, S>), Batch(Vec>), @@ -89,7 +91,7 @@ where impl GraphQLBatchRequest where - S: ScalarValue, + S: ScalarValue + Send + Sync, for<'b> &'b S: ScalarRefValue<'b>, { pub fn execute<'a, CtxT, QueryT, MutationT>( @@ -118,11 +120,14 @@ where pub async fn execute_async<'a, CtxT, QueryT, MutationT>( &'a self, root_node: &'a RootNode<'_, QueryT, MutationT, S>, - context: &CtxT, + context: &'a CtxT, ) -> GraphQLBatchResponse<'a, S> where - QueryT: GraphQLType, - MutationT: GraphQLType, + QueryT: GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, { match self { &GraphQLBatchRequest::Single(ref request) => { @@ -135,7 +140,7 @@ where .collect::>(); GraphQLBatchResponse::Batch(futures03::future::join_all(futures).await) - }, + } } } @@ -151,7 +156,7 @@ where impl<'a, S> GraphQLBatchResponse<'a, S> where - S: ScalarValue, + S: ScalarValue + Send + Sync, { fn is_ok(&self) -> bool { match self { @@ -171,7 +176,7 @@ where #[derive(Debug, PartialEq)] pub struct GraphQLRequest(GraphQLBatchRequest) where - S: ScalarValue; + S: ScalarValue + Send + Sync; /// Simple wrapper around the result of executing a GraphQL query pub struct GraphQLResponse(pub Status, pub String); @@ -190,7 +195,7 @@ pub fn playground_source(graphql_endpoint_url: &str) -> content::Html { impl GraphQLRequest where - S: ScalarValue, + S: ScalarValue + Sync + Send, for<'b> &'b S: ScalarRefValue<'b>, { /// Execute an incoming GraphQL query @@ -222,8 +227,11 @@ where context: &CtxT, ) -> GraphQLResponse where - QueryT: GraphQLType, - MutationT: GraphQLType, + QueryT: GraphQLTypeAsync + Send + Sync, + QueryT::TypeInfo: Send + Sync, + MutationT: GraphQLTypeAsync + Send + Sync, + MutationT::TypeInfo: Send + Sync, + CtxT: Send + Sync, { let response = self.0.execute_async(root_node, context).await; let status = if response.is_ok() { @@ -301,7 +309,7 @@ impl GraphQLResponse { impl<'f, S> FromForm<'f> for GraphQLRequest where - S: ScalarValue, + S: ScalarValue + Send + Sync, { type Error = String; @@ -372,7 +380,7 @@ where impl<'v, S> FromFormValue<'v> for GraphQLRequest where - S: ScalarValue, + S: ScalarValue + Send + Sync, { type Error = String; @@ -387,7 +395,7 @@ const BODY_LIMIT: u64 = 1024 * 100; impl FromDataSimple for GraphQLRequest where - S: ScalarValue, + S: ScalarValue + Send + Sync, { type Error = String; From 67480d176bfcfc54ed91b5890978b194676e80b1 Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Thu, 22 Aug 2019 10:38:01 +0100 Subject: [PATCH 22/25] Fix juniper issue --- juniper/src/types/async_await.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 12369bef..4297c630 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -5,6 +5,8 @@ use crate::value::{Object, ScalarRefValue, ScalarValue, Value}; use crate::executor::{ExecutionResult, Executor}; use crate::parser::Spanning; +use crate::BoxFuture; + use super::base::{is_excluded, merge_key_into, Arguments, GraphQLType}; pub trait GraphQLTypeAsync: GraphQLType + Send + Sync From c270c038ef19aa650ea47c7891a450e3b58b72b9 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Wed, 21 Aug 2019 15:23:53 +0200 Subject: [PATCH 23/25] Enable merge_imports rustfmt setting. style: Enable rustfmt merge_imports and format This commit enables the rustfmt merge_imports setting and formats the whole code base accordingly. Note that the setting is not stable yet, but will be with Rust 1.38. In the meantime, running fmt on stable will just leave the changes alone so no problems should occur. --- juniper/src/lib.rs | 2 +- juniper/src/types/async_await.rs | 13 ++++++++----- juniper_rocket/src/lib.rs | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 6317f4c6..943b8d51 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -180,7 +180,7 @@ pub use crate::{ pub type BoxFuture<'a, T> = std::pin::Pin + 'a + Send>>; #[cfg(feature = "async")] -pub use crate::types::async_await::{GraphQLTypeAsync}; +pub use crate::types::async_await::GraphQLTypeAsync; /// An error that prevented query execution #[derive(Debug, PartialEq)] diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 4297c630..ec11172f 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -1,9 +1,12 @@ +use crate::{ + ast::{Directive, FromInputValue, InputValue, Selection}, + value::{Object, ScalarRefValue, ScalarValue, Value}, +}; -use crate::ast::{Directive, FromInputValue, InputValue, Selection}; -use crate::value::{Object, ScalarRefValue, ScalarValue, Value}; - -use crate::executor::{ExecutionResult, Executor}; -use crate::parser::Spanning; +use crate::{ + executor::{ExecutionResult, Executor}, + parser::Spanning, +}; use crate::BoxFuture; diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 891a3723..88cf619d 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -42,14 +42,14 @@ Check the LICENSE file for details. use std::{ error::Error, - io::Cursor, + io::{Cursor, Read}, }; use rocket::{ - data::{FromDataFuture, FromDataSimple}, + data::{FromDataSimple, Outcome as FromDataOutcome}, http::{ContentType, RawStr, Status}, request::{FormItems, FromForm, FromFormValue}, - response::{content, Responder, Response, ResultFuture}, + response::{content, Responder, Response}, Data, Outcome::{Failure, Forward, Success}, Request, From 045c1870ef22974ee9fa05e1c83bd595aadcf407 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Mon, 26 Aug 2019 21:37:38 -0700 Subject: [PATCH 24/25] Replace `futures::future::FutureExt::boxed` with `Box::pin` --- juniper/src/macros/scalar.rs | 2 +- juniper/src/schema/schema.rs | 2 +- juniper/src/types/async_await.rs | 20 ++++++++++---------- juniper/src/types/containers.rs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/juniper/src/macros/scalar.rs b/juniper/src/macros/scalar.rs index 50753f3b..4f3ae84e 100644 --- a/juniper/src/macros/scalar.rs +++ b/juniper/src/macros/scalar.rs @@ -432,7 +432,7 @@ macro_rules! graphql_scalar { use $crate::GraphQLType; use futures::future; let v = self.resolve(info, selection_set, executor); - future::FutureExt::boxed(future::ready(v)) + Box::pin(future::ready(v)) } } ); diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index f71e9b18..14c3f2c2 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -99,7 +99,7 @@ where match field_name { "__schema" | "__type" => { let v = self.resolve_field(info, field_name, arguments, executor); - ready(v).boxed() + Box::pin(ready(v)) } _ => self .query_type diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index ec11172f..ab7b95d4 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -60,9 +60,12 @@ where 'e: 'a, for<'b> &'b S: ScalarRefValue<'b>, { - use futures::future::FutureExt; - - resolve_selection_set_into_async_recursive(instance, info, selection_set, executor).boxed() + Box::pin(resolve_selection_set_into_async_recursive( + instance, + info, + selection_set, + executor, + )) } struct AsyncField { @@ -89,10 +92,7 @@ where CtxT: Send + Sync, for<'b> &'b S: ScalarRefValue<'b>, { - use futures::{ - future::FutureExt, - stream::{FuturesOrdered, StreamExt}, - }; + use futures::stream::{FuturesOrdered, StreamExt}; let mut object = Object::with_capacity(selection_set.len()); @@ -183,7 +183,7 @@ where value, }) }; - async_values.push(field_future.boxed()); + async_values.push(Box::pin(field_future)); } Selection::FragmentSpread(Spanning { item: ref spread, .. @@ -206,7 +206,7 @@ where .await; AsyncValue::Nested(value) }; - async_values.push(f.boxed()); + async_values.push(Box::pin(f)); } Selection::InlineFragment(Spanning { item: ref fragment, @@ -250,7 +250,7 @@ where .await; AsyncValue::Nested(value) }; - async_values.push(f.boxed()); + async_values.push(Box::pin(f)); } } } diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 8004dbaf..06634072 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -272,7 +272,7 @@ where executor: &'a Executor, ) -> crate::BoxFuture<'a, Value> { let f = resolve_into_list_async(executor, info, self.iter()); - futures::future::FutureExt::boxed(f) + Box::pin(f) } } @@ -292,7 +292,7 @@ where executor: &'a Executor, ) -> crate::BoxFuture<'a, Value> { let f = resolve_into_list_async(executor, info, self.iter()); - futures::future::FutureExt::boxed(f) + Box::pin(f) } } @@ -317,6 +317,6 @@ where None => Value::null(), } }; - futures::future::FutureExt::boxed(f) + Box::pin(f) } } From 8d33e8db12a8479b9f4a5119c8ae89e5dc32381e Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Thu, 10 Oct 2019 07:14:45 +0200 Subject: [PATCH 25/25] Upgrade futures-preview, tokio crates and remove unnecessary 'async_await' feature (#436) --- examples/warp_async/Cargo.toml | 2 +- examples/warp_async/src/main.rs | 2 +- integration_tests/async_await/Cargo.toml | 4 ++-- integration_tests/async_await/src/main.rs | 6 +++--- juniper/Cargo.toml | 4 ++-- juniper/src/lib.rs | 2 +- juniper_benchmarks/Cargo.toml | 4 ++-- juniper_benchmarks/src/lib.rs | 2 +- juniper_rocket/Cargo.toml | 3 ++- juniper_rocket/src/lib.rs | 15 ++++++--------- juniper_warp/Cargo.toml | 2 +- juniper_warp/src/lib.rs | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml index c33445e8..7568b799 100644 --- a/examples/warp_async/Cargo.toml +++ b/examples/warp_async/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" log = "0.4.8" env_logger = "0.6.2" warp = "0.1.19" -futures-preview = { version = "0.3.0-alpha.18", features = ["nightly", "async-await", "compat"] } +futures-preview = { version = "0.3.0-alpha.19", features = ["async-await", "compat"] } reqwest = "0.9.19" juniper_codegen = { git = "https://github.com/graphql-rust/juniper", branch = "async-await", features = ["async"] } diff --git a/examples/warp_async/src/main.rs b/examples/warp_async/src/main.rs index 170d743b..6b53ff38 100644 --- a/examples/warp_async/src/main.rs +++ b/examples/warp_async/src/main.rs @@ -2,7 +2,7 @@ //! This example demonstrates async/await usage with warp. //! NOTE: this uses tokio 0.1 , not the alpha tokio 0.2. -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{EmptyMutation, RootNode, FieldError}; use warp::{http::Response, Filter}; diff --git a/integration_tests/async_await/Cargo.toml b/integration_tests/async_await/Cargo.toml index 3bbc0436..2207cf04 100644 --- a/integration_tests/async_await/Cargo.toml +++ b/integration_tests/async_await/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" [dependencies] juniper = { path = "../../juniper", features = ["async"] } -futures-preview = "0.3.0-alpha.18" -tokio = "0.2.0-alpha.2" +futures-preview = "=0.3.0-alpha.19" +tokio = "=0.2.0-alpha.6" diff --git a/integration_tests/async_await/src/main.rs b/integration_tests/async_await/src/main.rs index cbdfc4e3..eed72dff 100644 --- a/integration_tests/async_await/src/main.rs +++ b/integration_tests/async_await/src/main.rs @@ -1,4 +1,4 @@ -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{graphql_value, RootNode, Value}; @@ -38,7 +38,7 @@ impl User { async fn delayed() -> bool { let when = tokio::clock::now() + std::time::Duration::from_millis(100); - tokio::timer::Delay::new(when).await; + tokio::timer::delay(when).await; true } } @@ -65,7 +65,7 @@ impl Query { async fn delayed() -> bool { let when = tokio::clock::now() + std::time::Duration::from_millis(100); - tokio::timer::Delay::new(when).await; + tokio::timer::delay(when).await; true } } diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 7a57180d..f981af04 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -45,9 +45,9 @@ serde_json = { version="1.0.2", optional = true } url = { version = "2", optional = true } uuid = { version = "0.7", optional = true } -futures-preview = { version = "0.3.0-alpha.18", optional = true, features = ["nightly", "async-await"] } +futures-preview = { version = "=0.3.0-alpha.19", optional = true } [dev-dependencies] bencher = "0.1.2" serde_json = { version = "1.0.2" } -tokio = "0.2.0-alpha.2" +tokio = "=0.2.0-alpha.6" diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 943b8d51..e76efcaf 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -90,7 +90,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. */ #![doc(html_root_url = "https://docs.rs/juniper/0.14.0")] #![warn(missing_docs)] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] #[doc(hidden)] pub extern crate serde; diff --git a/juniper_benchmarks/Cargo.toml b/juniper_benchmarks/Cargo.toml index e4da5478..df10da20 100644 --- a/juniper_benchmarks/Cargo.toml +++ b/juniper_benchmarks/Cargo.toml @@ -12,8 +12,8 @@ harness = false [dependencies] juniper = { path = "../juniper", features = ["async"] } -futures-preview = "0.3.0-alpha.18" +futures-preview = "=0.3.0-alpha.19" [dev-dependencies] criterion = "0.2.11" -tokio = "0.2.0-alpha.2" +tokio = "=0.2.0-alpha.6" diff --git a/juniper_benchmarks/src/lib.rs b/juniper_benchmarks/src/lib.rs index a9f2aa4b..65c3b20a 100644 --- a/juniper_benchmarks/src/lib.rs +++ b/juniper_benchmarks/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(async_await, async_closure)] +#![feature(async_closure)] use juniper::{ object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables, diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index 1d8feda5..ad4cc5bc 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -20,8 +20,9 @@ serde_json = { version = "1.0.2" } serde_derive = { version = "1.0.2" } juniper = { version = "0.14.0", default-features = false, path = "../juniper"} -futures03 = { version = "0.3.0-alpha.18", package = "futures-preview", features = ["compat"] } +futures03 = { version = "=0.3.0-alpha.19", package = "futures-preview", features = ["compat"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } +tokio = "=0.2.0-alpha.6" [dev-dependencies.juniper] version = "0.14.0" diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 88cf619d..68817cb1 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -38,18 +38,15 @@ Check the LICENSE file for details. #![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")] #![feature(decl_macro, proc_macro_hygiene)] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] -use std::{ - error::Error, - io::{Cursor, Read}, -}; +use std::{error::Error, io::Cursor}; use rocket::{ - data::{FromDataSimple, Outcome as FromDataOutcome}, + data::{FromDataFuture, FromDataSimple}, http::{ContentType, RawStr, Status}, request::{FormItems, FromForm, FromFormValue}, - response::{content, Responder, Response}, + response::{content, Responder, Response, ResultFuture}, Data, Outcome::{Failure, Forward, Success}, Request, @@ -400,8 +397,8 @@ where type Error = String; fn from_data(request: &Request, data: Data) -> FromDataFuture<'static, Self, Self::Error> { - use futures03::io::AsyncReadExt; - use rocket::AsyncReadExt as _; + use tokio::io::AsyncReadExt as _; + if !request.content_type().map_or(false, |ct| ct.is_json()) { return Box::pin(async move { Forward(data) }); } diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index fc1eed48..a1c1907c 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -21,7 +21,7 @@ futures = "0.1.23" serde = "1.0.75" tokio-threadpool = "0.1.7" -futures03 = { version = "0.3.0-alpha.18", optional = true, package = "futures-preview", features = ["compat"] } +futures03 = { version = "=0.3.0-alpha.19", optional = true, package = "futures-preview", features = ["compat"] } [dev-dependencies] juniper = { version = "0.14.0", path = "../juniper", features = ["expose-test-schema", "serde_json"] } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index a8725b29..b00a9b6c 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -39,7 +39,7 @@ Check the LICENSE file for details. #![deny(missing_docs)] #![deny(warnings)] #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] -#![cfg_attr(feature = "async", feature(async_await, async_closure))] +#![cfg_attr(feature = "async", feature(async_closure))] use futures::{future::poll_fn, Future}; use serde::Deserialize;