Make the executor and validation APIs public to enable split parsing and execution (#874)
* 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 * Fix fmt warning for visit_all_rules * Add `Definition` to the public API so it's available for the result of compilation * Make OperationType public so that user land code can tell the difference between query/mutation and subscription * Add integrations tests for execute_validated_query_async, visit_all_rules, get_operation, and resolve_validated_subscription Co-authored-by: Christian Legnitto <LegNeato@users.noreply.github.com>
This commit is contained in:
parent
e7f7e7bff3
commit
c78045c167
11 changed files with 127 additions and 6 deletions
integration_tests/juniper_tests
juniper/src
|
@ -8,6 +8,7 @@ publish = false
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
juniper = { path = "../../juniper" }
|
juniper = { path = "../../juniper" }
|
||||||
|
juniper_subscriptions = { path = "../../juniper_subscriptions" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
async-trait = "0.1.39"
|
async-trait = "0.1.39"
|
||||||
|
|
|
@ -18,3 +18,5 @@ mod issue_398;
|
||||||
mod issue_407;
|
mod issue_407;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod issue_500;
|
mod issue_500;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod pre_parse;
|
||||||
|
|
102
integration_tests/juniper_tests/src/pre_parse.rs
Normal file
102
integration_tests/juniper_tests/src/pre_parse.rs
Normal file
|
@ -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<Box<dyn Stream<Item = Result<User, FieldError>> + Send>>;
|
||||||
|
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
#[graphql_object(context = Context)]
|
||||||
|
impl Query {
|
||||||
|
fn users() -> Vec<User> {
|
||||||
|
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<Context>, Subscription>;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn query_document_can_be_pre_parsed() {
|
||||||
|
let root_node = &Schema::new(Query, EmptyMutation::<Context>::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::<Context>::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();
|
||||||
|
}
|
|
@ -112,6 +112,7 @@ pub struct Directive<'a, S> {
|
||||||
pub arguments: Option<Spanning<Arguments<'a, S>>>,
|
pub arguments: Option<Spanning<Arguments<'a, S>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum OperationType {
|
pub enum OperationType {
|
||||||
Query,
|
Query,
|
||||||
|
@ -119,6 +120,7 @@ pub enum OperationType {
|
||||||
Subscription,
|
Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Operation<'a, S> {
|
pub struct Operation<'a, S> {
|
||||||
pub operation_type: OperationType,
|
pub operation_type: OperationType,
|
||||||
|
@ -136,12 +138,14 @@ pub struct Fragment<'a, S> {
|
||||||
pub selection_set: Vec<Selection<'a, S>>,
|
pub selection_set: Vec<Selection<'a, S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum Definition<'a, S> {
|
pub enum Definition<'a, S> {
|
||||||
Operation(Spanning<Operation<'a, S>>),
|
Operation(Spanning<Operation<'a, S>>),
|
||||||
Fragment(Spanning<Fragment<'a, S>>),
|
Fragment(Spanning<Fragment<'a, S>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub type Document<'a, S> = Vec<Definition<'a, S>>;
|
pub type Document<'a, S> = Vec<Definition<'a, S>>;
|
||||||
|
|
||||||
/// Parse an unstructured input value into a Rust data type.
|
/// Parse an unstructured input value into a Rust data type.
|
||||||
|
|
|
@ -96,6 +96,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ChildSelection<'a, S: 'a> {
|
pub struct ChildSelection<'a, S: 'a> {
|
||||||
pub(super) inner: LookAheadSelection<'a, S>,
|
pub(super) inner: LookAheadSelection<'a, S>,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Resolve the document to values
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
@ -54,6 +56,7 @@ pub struct Registry<'r, S = DefaultScalarValue> {
|
||||||
pub types: FnvHashMap<Name, MetaType<'r, S>>,
|
pub types: FnvHashMap<Name, MetaType<'r, S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum FieldPath<'a> {
|
pub enum FieldPath<'a> {
|
||||||
Root(SourcePosition),
|
Root(SourcePosition),
|
||||||
|
@ -979,6 +982,7 @@ where
|
||||||
Ok((value, errors))
|
Ok((value, errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn get_operation<'b, 'd, 'e, S>(
|
pub fn get_operation<'b, 'd, 'e, S>(
|
||||||
document: &'b Document<'d, S>,
|
document: &'b Document<'d, S>,
|
||||||
operation_name: Option<&str>,
|
operation_name: Option<&str>,
|
||||||
|
|
|
@ -119,13 +119,13 @@ mod value;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod ast;
|
mod ast;
|
||||||
mod executor;
|
pub mod executor;
|
||||||
mod introspection;
|
mod introspection;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub(crate) mod schema;
|
pub(crate) mod schema;
|
||||||
mod types;
|
mod types;
|
||||||
mod util;
|
mod util;
|
||||||
mod validation;
|
pub mod validation;
|
||||||
// This needs to be public until docs have support for private modules:
|
// This needs to be public until docs have support for private modules:
|
||||||
// https://github.com/rust-lang/cargo/issues/1520
|
// https://github.com/rust-lang/cargo/issues/1520
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
@ -145,12 +145,15 @@ pub use crate::util::to_camel_case;
|
||||||
use crate::{
|
use crate::{
|
||||||
executor::{execute_validated_query, get_operation},
|
executor::{execute_validated_query, get_operation},
|
||||||
introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS},
|
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},
|
validation::{validate_input_values, visit_all_rules, ValidatorContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
|
ast::{
|
||||||
|
Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection,
|
||||||
|
ToInputValue, Type,
|
||||||
|
},
|
||||||
executor::{
|
executor::{
|
||||||
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
|
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
|
||||||
FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
|
FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
|
||||||
|
@ -161,6 +164,7 @@ pub use crate::{
|
||||||
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
||||||
AsDynGraphQLValue,
|
AsDynGraphQLValue,
|
||||||
},
|
},
|
||||||
|
parser::{ParseError, Spanning},
|
||||||
schema::{
|
schema::{
|
||||||
meta,
|
meta,
|
||||||
model::{RootNode, SchemaType},
|
model::{RootNode, SchemaType},
|
||||||
|
|
|
@ -19,6 +19,7 @@ enum Path<'a> {
|
||||||
ObjectField(&'a str, &'a Path<'a>),
|
ObjectField(&'a str, &'a Path<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn validate_input_values<S>(
|
pub fn validate_input_values<S>(
|
||||||
values: &Variables<S>,
|
values: &Variables<S>,
|
||||||
operation: &Spanning<Operation<S>>,
|
operation: &Spanning<Operation<S>>,
|
||||||
|
|
|
@ -10,11 +10,11 @@ mod visitor;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_harness;
|
pub(crate) mod test_harness;
|
||||||
|
|
||||||
pub(crate) use self::rules::visit_all_rules;
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
context::{RuleError, ValidatorContext},
|
context::{RuleError, ValidatorContext},
|
||||||
input_value::validate_input_values,
|
input_value::validate_input_values,
|
||||||
multi_visitor::MultiVisitorNil,
|
multi_visitor::MultiVisitorNil,
|
||||||
|
rules::visit_all_rules,
|
||||||
traits::Visitor,
|
traits::Visitor,
|
||||||
visitor::visit,
|
visitor::visit,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct MultiVisitorNil;
|
pub struct MultiVisitorNil;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
impl MultiVisitorNil {
|
impl MultiVisitorNil {
|
||||||
pub fn with<V>(self, visitor: V) -> MultiVisitorCons<V, Self> {
|
pub fn with<V>(self, visitor: V) -> MultiVisitorCons<V, Self> {
|
||||||
MultiVisitorCons(visitor, self)
|
MultiVisitorCons(visitor, self)
|
||||||
|
|
|
@ -30,7 +30,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub(crate) fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
|
#[doc(hidden)]
|
||||||
|
pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue