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
|
@ -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"
|
||||
|
|
|
@ -18,3 +18,5 @@ mod issue_398;
|
|||
mod issue_407;
|
||||
#[cfg(test)]
|
||||
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>>>,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum OperationType {
|
||||
Query,
|
||||
|
@ -119,6 +120,7 @@ pub enum OperationType {
|
|||
Subscription,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Operation<'a, S> {
|
||||
pub operation_type: OperationType,
|
||||
|
@ -136,12 +138,14 @@ pub struct Fragment<'a, S> {
|
|||
pub selection_set: Vec<Selection<'a, S>>,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Definition<'a, S> {
|
||||
Operation(Spanning<Operation<'a, S>>),
|
||||
Fragment(Spanning<Fragment<'a, S>>),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub type Document<'a, S> = Vec<Definition<'a, S>>;
|
||||
|
||||
/// Parse an unstructured input value into a Rust data type.
|
||||
|
|
|
@ -96,6 +96,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ChildSelection<'a, S: 'a> {
|
||||
pub(super) inner: LookAheadSelection<'a, S>,
|
||||
|
|
|
@ -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<Name, MetaType<'r, S>>,
|
||||
}
|
||||
|
||||
#[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>,
|
||||
|
|
|
@ -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,15 @@ 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::{
|
||||
Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection,
|
||||
ToInputValue, Type,
|
||||
},
|
||||
executor::{
|
||||
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
|
||||
FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
|
||||
|
@ -161,6 +164,7 @@ pub use crate::{
|
|||
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
||||
AsDynGraphQLValue,
|
||||
},
|
||||
parser::{ParseError, Spanning},
|
||||
schema::{
|
||||
meta,
|
||||
model::{RootNode, SchemaType},
|
||||
|
|
|
@ -19,6 +19,7 @@ enum Path<'a> {
|
|||
ObjectField(&'a str, &'a Path<'a>),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn validate_input_values<S>(
|
||||
values: &Variables<S>,
|
||||
operation: &Spanning<Operation<S>>,
|
||||
|
|
|
@ -10,11 +10,11 @@ mod visitor;
|
|||
#[cfg(test)]
|
||||
pub(crate) mod test_harness;
|
||||
|
||||
pub(crate) 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,
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
|||
#[doc(hidden)]
|
||||
pub struct MultiVisitorNil;
|
||||
|
||||
#[doc(hidden)]
|
||||
impl MultiVisitorNil {
|
||||
pub fn with<V>(self, visitor: V) -> MultiVisitorCons<V, Self> {
|
||||
MultiVisitorCons(visitor, self)
|
||||
|
|
|
@ -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<S>)
|
||||
#[doc(hidden)]
|
||||
pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue