From c358d5ed0c9b4c04ae7b5ae78382dff86bddd4c7 Mon Sep 17 00:00:00 2001 From: Jerel Unruh Date: Fri, 12 Feb 2021 18:29:37 -0600 Subject: [PATCH 1/5] Make the executor and validation APIs public to enable splitting parsing and execution into two stages Based on https://github.com/graphql-rust/juniper/pull/773#issuecomment-703783048 and https://github.com/graphql-rust/juniper/pull/773#issuecomment-704009918 --- juniper/src/ast.rs | 2 ++ juniper/src/executor/look_ahead.rs | 1 + juniper/src/executor/mod.rs | 4 ++++ juniper/src/lib.rs | 9 +++++---- juniper/src/validation/input_value.rs | 1 + juniper/src/validation/mod.rs | 2 +- juniper/src/validation/multi_visitor.rs | 1 + juniper/src/validation/rules/mod.rs | 3 ++- 8 files changed, 17 insertions(+), 6 deletions(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 4d410883f..f01862f49 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -119,6 +119,7 @@ pub enum OperationType { Subscription, } +#[allow(missing_docs)] #[derive(Clone, PartialEq, Debug)] pub struct Operation<'a, S> { pub operation_type: OperationType, @@ -142,6 +143,7 @@ pub enum Definition<'a, S> { Fragment(Spanning>), } +#[doc(hidden)] pub type Document<'a, S> = Vec>; /// Parse an unstructured input value into a Rust data type. diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 682610ebc..0dd37c5ed 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -96,6 +96,7 @@ where } } +#[doc(hidden)] #[derive(Debug, Clone, PartialEq)] pub struct ChildSelection<'a, S: 'a> { pub(super) inner: LookAheadSelection<'a, S>, diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 527e5540f..ed7db8023 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -1,3 +1,5 @@ +//! Resolve the document to values + use std::{ borrow::Cow, cmp::Ordering, @@ -54,6 +56,7 @@ pub struct Registry<'r, S = DefaultScalarValue> { pub types: FnvHashMap>, } +#[allow(missing_docs)] #[derive(Clone)] pub enum FieldPath<'a> { Root(SourcePosition), @@ -979,6 +982,7 @@ where Ok((value, errors)) } +#[doc(hidden)] pub fn get_operation<'b, 'd, 'e, S>( document: &'b Document<'d, S>, operation_name: Option<&str>, diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index bce28cc16..ae92d3452 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -119,13 +119,13 @@ mod value; #[macro_use] mod macros; mod ast; -mod executor; +pub mod executor; mod introspection; pub mod parser; pub(crate) mod schema; mod types; mod util; -mod validation; +pub mod validation; // This needs to be public until docs have support for private modules: // https://github.com/rust-lang/cargo/issues/1520 pub mod http; @@ -145,12 +145,12 @@ pub use crate::util::to_camel_case; use crate::{ executor::{execute_validated_query, get_operation}, introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS}, - parser::{parse_document_source, ParseError, Spanning}, + parser::parse_document_source, validation::{validate_input_values, visit_all_rules, ValidatorContext}, }; pub use crate::{ - ast::{FromInputValue, InputValue, Selection, ToInputValue, Type}, + ast::{Document, FromInputValue, InputValue, Operation, Selection, ToInputValue, Type}, executor::{ Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods, @@ -161,6 +161,7 @@ pub use crate::{ subscription::{ExtractTypeFromStream, IntoFieldResult}, AsDynGraphQLValue, }, + parser::{ParseError, Spanning}, schema::{ meta, model::{RootNode, SchemaType}, diff --git a/juniper/src/validation/input_value.rs b/juniper/src/validation/input_value.rs index aa87fd1ce..d2e26a852 100644 --- a/juniper/src/validation/input_value.rs +++ b/juniper/src/validation/input_value.rs @@ -19,6 +19,7 @@ enum Path<'a> { ObjectField(&'a str, &'a Path<'a>), } +#[doc(hidden)] pub fn validate_input_values( values: &Variables, operation: &Spanning>, diff --git a/juniper/src/validation/mod.rs b/juniper/src/validation/mod.rs index 61afae269..efd2f2b2c 100644 --- a/juniper/src/validation/mod.rs +++ b/juniper/src/validation/mod.rs @@ -10,7 +10,7 @@ mod visitor; #[cfg(test)] pub(crate) mod test_harness; -pub(crate) use self::rules::visit_all_rules; +pub use self::rules::visit_all_rules; pub use self::{ context::{RuleError, ValidatorContext}, input_value::validate_input_values, diff --git a/juniper/src/validation/multi_visitor.rs b/juniper/src/validation/multi_visitor.rs index 9a25abc40..5f76802d7 100644 --- a/juniper/src/validation/multi_visitor.rs +++ b/juniper/src/validation/multi_visitor.rs @@ -11,6 +11,7 @@ use crate::{ #[doc(hidden)] pub struct MultiVisitorNil; +#[doc(hidden)] impl MultiVisitorNil { pub fn with(self, visitor: V) -> MultiVisitorCons { MultiVisitorCons(visitor, self) diff --git a/juniper/src/validation/rules/mod.rs b/juniper/src/validation/rules/mod.rs index 8eae5a137..211c17260 100644 --- a/juniper/src/validation/rules/mod.rs +++ b/juniper/src/validation/rules/mod.rs @@ -30,7 +30,8 @@ use crate::{ }; use std::fmt::Debug; -pub(crate) fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document) +#[doc(hidden)] +pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document) where S: ScalarValue, { From f984237e4c172574cf2e5e7ed1934beb80214d27 Mon Sep 17 00:00:00 2001 From: Jerel Unruh Date: Fri, 12 Feb 2021 20:11:35 -0600 Subject: [PATCH 2/5] Fix fmt warning for visit_all_rules --- juniper/src/schema/meta.rs | 10 ++++++++-- juniper/src/validation/mod.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 9d1342d78..518859f1a 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -348,7 +348,10 @@ impl<'a, S> MetaType<'a, S> { /// /// Objects, interfaces, and unions are composite. pub fn is_composite(&self) -> bool { - matches!(*self, MetaType::Object(_) | MetaType::Interface(_) | MetaType::Union(_)) + matches!( + *self, + MetaType::Object(_) | MetaType::Interface(_) | MetaType::Union(_) + ) } /// Returns true if the type can occur in leaf positions in queries @@ -369,7 +372,10 @@ impl<'a, S> MetaType<'a, S> { /// /// Only scalars, enums, and input objects are input types. pub fn is_input(&self) -> bool { - matches!(*self, MetaType::Scalar(_) | MetaType::Enum(_) | MetaType::InputObject(_)) + matches!( + *self, + MetaType::Scalar(_) | MetaType::Enum(_) | MetaType::InputObject(_) + ) } /// Returns true if the type is built-in to GraphQL. diff --git a/juniper/src/validation/mod.rs b/juniper/src/validation/mod.rs index efd2f2b2c..4a5435286 100644 --- a/juniper/src/validation/mod.rs +++ b/juniper/src/validation/mod.rs @@ -10,11 +10,11 @@ mod visitor; #[cfg(test)] pub(crate) mod test_harness; -pub use self::rules::visit_all_rules; pub use self::{ context::{RuleError, ValidatorContext}, input_value::validate_input_values, multi_visitor::MultiVisitorNil, + rules::visit_all_rules, traits::Visitor, visitor::visit, }; From 1cac83209c3dbe1332a6b190e1494ca725f4b540 Mon Sep 17 00:00:00 2001 From: Jerel Unruh Date: Tue, 2 Mar 2021 14:23:46 -0600 Subject: [PATCH 3/5] Add `Definition` to the public API so it's available for the result of compilation --- juniper/src/ast.rs | 1 + juniper/src/lib.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index f01862f49..70695fbb6 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -137,6 +137,7 @@ pub struct Fragment<'a, S> { pub selection_set: Vec>, } +#[doc(hidden)] #[derive(Clone, PartialEq, Debug)] pub enum Definition<'a, S> { Operation(Spanning>), diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index ae92d3452..a9210a553 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -150,7 +150,9 @@ use crate::{ }; pub use crate::{ - ast::{Document, FromInputValue, InputValue, Operation, Selection, ToInputValue, Type}, + ast::{ + Definition, Document, FromInputValue, InputValue, Operation, Selection, ToInputValue, Type, + }, executor::{ Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods, From a860889511e92d1dc3e44eba9ba097505fd70ac4 Mon Sep 17 00:00:00 2001 From: Jerel Unruh Date: Thu, 11 Mar 2021 11:08:50 -0600 Subject: [PATCH 4/5] Make OperationType public so that user land code can tell the difference between query/mutation and subscription --- juniper/src/ast.rs | 1 + juniper/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 70695fbb6..88962f9b5 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -112,6 +112,7 @@ pub struct Directive<'a, S> { pub arguments: Option>>, } +#[allow(missing_docs)] #[derive(Clone, PartialEq, Debug)] pub enum OperationType { Query, diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index a9210a553..8b2701c59 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -151,7 +151,8 @@ use crate::{ pub use crate::{ ast::{ - Definition, Document, FromInputValue, InputValue, Operation, Selection, ToInputValue, Type, + Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection, + ToInputValue, Type, }, executor::{ Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, From 618cc1b414af3f9bee4489e84aa9c0a311a5866f Mon Sep 17 00:00:00 2001 From: Jerel Unruh Date: Fri, 2 Apr 2021 16:03:21 -0500 Subject: [PATCH 5/5] Add integrations tests for execute_validated_query_async, visit_all_rules, get_operation, and resolve_validated_subscription --- integration_tests/juniper_tests/Cargo.toml | 1 + integration_tests/juniper_tests/src/lib.rs | 2 + .../juniper_tests/src/pre_parse.rs | 102 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 integration_tests/juniper_tests/src/pre_parse.rs diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml index c26e38427..1acfeb190 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/integration_tests/juniper_tests/Cargo.toml @@ -8,6 +8,7 @@ publish = false derive_more = "0.99" futures = "0.3" juniper = { path = "../../juniper" } +juniper_subscriptions = { path = "../../juniper_subscriptions" } [dev-dependencies] async-trait = "0.1.39" diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index d4f0e94be..64d8c1642 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -16,3 +16,5 @@ mod issue_371; mod issue_398; #[cfg(test)] mod issue_500; +#[cfg(test)] +mod pre_parse; diff --git a/integration_tests/juniper_tests/src/pre_parse.rs b/integration_tests/juniper_tests/src/pre_parse.rs new file mode 100644 index 000000000..c18b1e32a --- /dev/null +++ b/integration_tests/juniper_tests/src/pre_parse.rs @@ -0,0 +1,102 @@ +use futures::{Stream, StreamExt, TryFutureExt}; +use juniper::{ + executor::{execute_validated_query_async, get_operation, resolve_validated_subscription}, + graphql_object, graphql_subscription, + parser::parse_document_source, + validation::{validate_input_values, visit_all_rules, ValidatorContext}, + EmptyMutation, FieldError, OperationType, RootNode, Variables, +}; +use std::pin::Pin; + +pub struct Context; + +impl juniper::Context for Context {} + +pub type UserStream = Pin> + Send>>; + +pub struct Query; + +#[graphql_object(context = Context)] +impl Query { + fn users() -> Vec { + vec![User] + } +} + +pub struct Subscription; + +#[graphql_subscription(context = Context)] +impl Subscription { + async fn users() -> UserStream { + Box::pin(futures::stream::iter(vec![Ok(User)])) + } +} + +#[derive(Clone)] +pub struct User; + +#[graphql_object(context = Context)] +impl User { + fn id() -> i32 { + 1 + } +} + +type Schema = RootNode<'static, Query, EmptyMutation, Subscription>; + +#[tokio::test] +async fn query_document_can_be_pre_parsed() { + let root_node = &Schema::new(Query, EmptyMutation::::new(), Subscription); + + let document_source = r#"query { users { id } }"#; + let document = parse_document_source(document_source, &root_node.schema).unwrap(); + + { + let mut ctx = ValidatorContext::new(&root_node.schema, &document); + visit_all_rules(&mut ctx, &document); + let errors = ctx.into_errors(); + assert!(errors.is_empty()); + } + + let operation = get_operation(&document, None).unwrap(); + assert!(operation.item.operation_type == OperationType::Query); + + let errors = validate_input_values(&juniper::Variables::new(), operation, &root_node.schema); + assert!(errors.is_empty()); + + let (_, errors) = execute_validated_query_async( + &document, + operation, + root_node, + &Variables::new(), + &Context {}, + ) + .await + .unwrap(); + + assert!(errors.len() == 0); +} + +#[tokio::test] +async fn subscription_document_can_be_pre_parsed() { + let root_node = &Schema::new(Query, EmptyMutation::::new(), Subscription); + + let document_source = r#"subscription { users { id } }"#; + let document = parse_document_source(document_source, &root_node.schema).unwrap(); + + let operation = get_operation(&document, None).unwrap(); + assert!(operation.item.operation_type == OperationType::Subscription); + + let mut stream = resolve_validated_subscription( + &document, + &operation, + &root_node, + &Variables::new(), + &Context {}, + ) + .map_ok(|(stream, errors)| juniper_subscriptions::Connection::from_stream(stream, errors)) + .await + .unwrap(); + + let _ = stream.next().await.unwrap(); +}