Skip to content

Commit c78045c

Browse files
jerelLegNeato
andauthored
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 #773 (comment) and #773 (comment) * 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 <[email protected]>
1 parent e7f7e7b commit c78045c

File tree

11 files changed

+127
-6
lines changed

11 files changed

+127
-6
lines changed

integration_tests/juniper_tests/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ publish = false
88
derive_more = "0.99"
99
futures = "0.3"
1010
juniper = { path = "../../juniper" }
11+
juniper_subscriptions = { path = "../../juniper_subscriptions" }
1112

1213
[dev-dependencies]
1314
async-trait = "0.1.39"

integration_tests/juniper_tests/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ mod issue_398;
1818
mod issue_407;
1919
#[cfg(test)]
2020
mod issue_500;
21+
#[cfg(test)]
22+
mod pre_parse;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use futures::{Stream, StreamExt, TryFutureExt};
2+
use juniper::{
3+
executor::{execute_validated_query_async, get_operation, resolve_validated_subscription},
4+
graphql_object, graphql_subscription,
5+
parser::parse_document_source,
6+
validation::{validate_input_values, visit_all_rules, ValidatorContext},
7+
EmptyMutation, FieldError, OperationType, RootNode, Variables,
8+
};
9+
use std::pin::Pin;
10+
11+
pub struct Context;
12+
13+
impl juniper::Context for Context {}
14+
15+
pub type UserStream = Pin<Box<dyn Stream<Item = Result<User, FieldError>> + Send>>;
16+
17+
pub struct Query;
18+
19+
#[graphql_object(context = Context)]
20+
impl Query {
21+
fn users() -> Vec<User> {
22+
vec![User]
23+
}
24+
}
25+
26+
pub struct Subscription;
27+
28+
#[graphql_subscription(context = Context)]
29+
impl Subscription {
30+
async fn users() -> UserStream {
31+
Box::pin(futures::stream::iter(vec![Ok(User)]))
32+
}
33+
}
34+
35+
#[derive(Clone)]
36+
pub struct User;
37+
38+
#[graphql_object(context = Context)]
39+
impl User {
40+
fn id() -> i32 {
41+
1
42+
}
43+
}
44+
45+
type Schema = RootNode<'static, Query, EmptyMutation<Context>, Subscription>;
46+
47+
#[tokio::test]
48+
async fn query_document_can_be_pre_parsed() {
49+
let root_node = &Schema::new(Query, EmptyMutation::<Context>::new(), Subscription);
50+
51+
let document_source = r#"query { users { id } }"#;
52+
let document = parse_document_source(document_source, &root_node.schema).unwrap();
53+
54+
{
55+
let mut ctx = ValidatorContext::new(&root_node.schema, &document);
56+
visit_all_rules(&mut ctx, &document);
57+
let errors = ctx.into_errors();
58+
assert!(errors.is_empty());
59+
}
60+
61+
let operation = get_operation(&document, None).unwrap();
62+
assert!(operation.item.operation_type == OperationType::Query);
63+
64+
let errors = validate_input_values(&juniper::Variables::new(), operation, &root_node.schema);
65+
assert!(errors.is_empty());
66+
67+
let (_, errors) = execute_validated_query_async(
68+
&document,
69+
operation,
70+
root_node,
71+
&Variables::new(),
72+
&Context {},
73+
)
74+
.await
75+
.unwrap();
76+
77+
assert!(errors.len() == 0);
78+
}
79+
80+
#[tokio::test]
81+
async fn subscription_document_can_be_pre_parsed() {
82+
let root_node = &Schema::new(Query, EmptyMutation::<Context>::new(), Subscription);
83+
84+
let document_source = r#"subscription { users { id } }"#;
85+
let document = parse_document_source(document_source, &root_node.schema).unwrap();
86+
87+
let operation = get_operation(&document, None).unwrap();
88+
assert!(operation.item.operation_type == OperationType::Subscription);
89+
90+
let mut stream = resolve_validated_subscription(
91+
&document,
92+
&operation,
93+
&root_node,
94+
&Variables::new(),
95+
&Context {},
96+
)
97+
.map_ok(|(stream, errors)| juniper_subscriptions::Connection::from_stream(stream, errors))
98+
.await
99+
.unwrap();
100+
101+
let _ = stream.next().await.unwrap();
102+
}

juniper/src/ast.rs

+4
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ pub struct Directive<'a, S> {
112112
pub arguments: Option<Spanning<Arguments<'a, S>>>,
113113
}
114114

115+
#[allow(missing_docs)]
115116
#[derive(Clone, PartialEq, Debug)]
116117
pub enum OperationType {
117118
Query,
118119
Mutation,
119120
Subscription,
120121
}
121122

123+
#[allow(missing_docs)]
122124
#[derive(Clone, PartialEq, Debug)]
123125
pub struct Operation<'a, S> {
124126
pub operation_type: OperationType,
@@ -136,12 +138,14 @@ pub struct Fragment<'a, S> {
136138
pub selection_set: Vec<Selection<'a, S>>,
137139
}
138140

141+
#[doc(hidden)]
139142
#[derive(Clone, PartialEq, Debug)]
140143
pub enum Definition<'a, S> {
141144
Operation(Spanning<Operation<'a, S>>),
142145
Fragment(Spanning<Fragment<'a, S>>),
143146
}
144147

148+
#[doc(hidden)]
145149
pub type Document<'a, S> = Vec<Definition<'a, S>>;
146150

147151
/// Parse an unstructured input value into a Rust data type.

juniper/src/executor/look_ahead.rs

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ where
9696
}
9797
}
9898

99+
#[doc(hidden)]
99100
#[derive(Debug, Clone, PartialEq)]
100101
pub struct ChildSelection<'a, S: 'a> {
101102
pub(super) inner: LookAheadSelection<'a, S>,

juniper/src/executor/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Resolve the document to values
2+
13
use std::{
24
borrow::Cow,
35
cmp::Ordering,
@@ -54,6 +56,7 @@ pub struct Registry<'r, S = DefaultScalarValue> {
5456
pub types: FnvHashMap<Name, MetaType<'r, S>>,
5557
}
5658

59+
#[allow(missing_docs)]
5760
#[derive(Clone)]
5861
pub enum FieldPath<'a> {
5962
Root(SourcePosition),
@@ -979,6 +982,7 @@ where
979982
Ok((value, errors))
980983
}
981984

985+
#[doc(hidden)]
982986
pub fn get_operation<'b, 'd, 'e, S>(
983987
document: &'b Document<'d, S>,
984988
operation_name: Option<&str>,

juniper/src/lib.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,13 @@ mod value;
119119
#[macro_use]
120120
mod macros;
121121
mod ast;
122-
mod executor;
122+
pub mod executor;
123123
mod introspection;
124124
pub mod parser;
125125
pub(crate) mod schema;
126126
mod types;
127127
mod util;
128-
mod validation;
128+
pub mod validation;
129129
// This needs to be public until docs have support for private modules:
130130
// https://github.com/rust-lang/cargo/issues/1520
131131
pub mod http;
@@ -145,12 +145,15 @@ pub use crate::util::to_camel_case;
145145
use crate::{
146146
executor::{execute_validated_query, get_operation},
147147
introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS},
148-
parser::{parse_document_source, ParseError, Spanning},
148+
parser::parse_document_source,
149149
validation::{validate_input_values, visit_all_rules, ValidatorContext},
150150
};
151151

152152
pub use crate::{
153-
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
153+
ast::{
154+
Definition, Document, FromInputValue, InputValue, Operation, OperationType, Selection,
155+
ToInputValue, Type,
156+
},
154157
executor::{
155158
Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
156159
FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods,
@@ -161,6 +164,7 @@ pub use crate::{
161164
subscription::{ExtractTypeFromStream, IntoFieldResult},
162165
AsDynGraphQLValue,
163166
},
167+
parser::{ParseError, Spanning},
164168
schema::{
165169
meta,
166170
model::{RootNode, SchemaType},

juniper/src/validation/input_value.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ enum Path<'a> {
1919
ObjectField(&'a str, &'a Path<'a>),
2020
}
2121

22+
#[doc(hidden)]
2223
pub fn validate_input_values<S>(
2324
values: &Variables<S>,
2425
operation: &Spanning<Operation<S>>,

juniper/src/validation/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ mod visitor;
1010
#[cfg(test)]
1111
pub(crate) mod test_harness;
1212

13-
pub(crate) use self::rules::visit_all_rules;
1413
pub use self::{
1514
context::{RuleError, ValidatorContext},
1615
input_value::validate_input_values,
1716
multi_visitor::MultiVisitorNil,
17+
rules::visit_all_rules,
1818
traits::Visitor,
1919
visitor::visit,
2020
};

juniper/src/validation/multi_visitor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
#[doc(hidden)]
1212
pub struct MultiVisitorNil;
1313

14+
#[doc(hidden)]
1415
impl MultiVisitorNil {
1516
pub fn with<V>(self, visitor: V) -> MultiVisitorCons<V, Self> {
1617
MultiVisitorCons(visitor, self)

juniper/src/validation/rules/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ use crate::{
3030
};
3131
use std::fmt::Debug;
3232

33-
pub(crate) fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
33+
#[doc(hidden)]
34+
pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
3435
where
3536
S: ScalarValue,
3637
{

0 commit comments

Comments
 (0)