diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml
index c26e3842..1acfeb19 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 ac40e335..f6bfb683 100644
--- a/integration_tests/juniper_tests/src/lib.rs
+++ b/integration_tests/juniper_tests/src/lib.rs
@@ -18,3 +18,5 @@ mod issue_398;
 mod issue_407;
 #[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 00000000..c18b1e32
--- /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<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();
+}
diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs
index 4d410883..88962f9b 100644
--- a/juniper/src/ast.rs
+++ b/juniper/src/ast.rs
@@ -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.
diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs
index 02edbf15..68dfac2b 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 3bbb204c..7a33e88b 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<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>,
diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs
index ab978950..f07d54b9 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,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},
diff --git a/juniper/src/validation/input_value.rs b/juniper/src/validation/input_value.rs
index aa87fd1c..d2e26a85 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<S>(
     values: &Variables<S>,
     operation: &Spanning<Operation<S>>,
diff --git a/juniper/src/validation/mod.rs b/juniper/src/validation/mod.rs
index 61afae26..4a543528 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(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,
 };
diff --git a/juniper/src/validation/multi_visitor.rs b/juniper/src/validation/multi_visitor.rs
index 9a25abc4..5f76802d 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<V>(self, visitor: V) -> MultiVisitorCons<V, Self> {
         MultiVisitorCons(visitor, self)
diff --git a/juniper/src/validation/rules/mod.rs b/juniper/src/validation/rules/mod.rs
index 8eae5a13..211c1726 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<S>)
+#[doc(hidden)]
+pub fn visit_all_rules<'a, S: Debug>(ctx: &mut ValidatorContext<'a, S>, doc: &'a Document<S>)
 where
     S: ScalarValue,
 {