Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- `ruint::aliases::U128` as `U128` scalar.
- `ruint::aliases::U256` as `U256` scalar.
- `integrations::ruint::unit_scalar` module for declaring custom-sized `ruint::Unit` scalars.
- `parse_and_validate()` function for parsing and validating a query without executing it, returning the `OperationType` that would be executed. ([#1392], [#726])

### Changed

Expand Down Expand Up @@ -86,6 +87,8 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1378]: /../../pull/1378
[#1380]: /../../pull/1380
[#1387]: /../../pull/1387
[#1392]: /../../pull/1392
[#726]: /../../issues/726
[graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525
[graphql/graphql-spec#687]: https://github.com/graphql/graphql-spec/issues/687
[graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805
Expand Down
53 changes: 53 additions & 0 deletions juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,59 @@ where
execute_validated_query(&document, operation, root_node, variables, context)
}

/// Parses and validates a GraphQL query against the provided schema without
/// executing it, returning the [`OperationType`] that would be executed.
///
/// This runs the same parsing and validation steps as [`execute()`] and
/// [`execute_sync()`], but stops short of resolving anything. It's useful when
/// you need to know whether a request is a query, mutation or subscription
/// before deciding how to run it, e.g. to route subscriptions to
/// [`resolve_into_stream()`] and everything else to [`execute()`], without
/// having to attempt execution first.
pub fn parse_and_validate<'a, S, QueryT, MutationT, SubscriptionT>(
document_source: &'a str,
operation_name: Option<&str>,
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
variables: &Variables<S>,
) -> Result<OperationType, GraphQLError>
where
S: ScalarValue,
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S, Context = QueryT::Context>,
SubscriptionT: GraphQLType<S, Context = QueryT::Context>,
{
let document = parse_document_source(document_source, &root_node.schema)?;

{
let mut ctx = ValidatorContext::new(&root_node.schema, &document);
visit_all_rules(&mut ctx, &document);
if root_node.introspection_disabled {
visit_rule(
&mut MultiVisitorNil.with(rules::disable_introspection::factory()),
&mut ctx,
&document,
);
}

let errors = ctx.into_errors();
if !errors.is_empty() {
return Err(errors.into());
}
}

let operation = get_operation(&document, operation_name)?;

{
let errors = validate_input_values(variables, operation, &root_node.schema);

if !errors.is_empty() {
return Err(errors.into());
}
}

Ok(operation.item.operation_type)
}

/// Execute a query in a provided schema
pub async fn execute<'a, S, QueryT, MutationT, SubscriptionT>(
document_source: &'a str,
Expand Down
2 changes: 2 additions & 0 deletions juniper/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod introspection_tests;
#[cfg(test)]
mod operation_not_supported;
#[cfg(test)]
mod parse_and_validate;
#[cfg(test)]
mod query_tests;
#[cfg(test)]
mod schema_introspection;
Expand Down
112 changes: 112 additions & 0 deletions juniper/src/tests/parse_and_validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Tests for [`parse_and_validate()`].
//!
//! [`parse_and_validate()`] should run the same parsing and validation as
//! [`execute()`]/[`execute_sync()`], report the same errors, and report the
//! [`OperationType`] of the operation that would be executed.
//!
//! [`execute()`]: crate::execute
//! [`execute_sync()`]: crate::execute_sync
//! [`OperationType`]: crate::ast::OperationType
//! [`parse_and_validate()`]: crate::parse_and_validate

use std::pin::Pin;

use futures::stream;

use crate::{
Context, DefaultScalarValue, GraphQLError, RootNode, ast::OperationType, graphql,
graphql_object, graphql_subscription,
};

struct MyContext;
impl Context for MyContext {}

struct Query;

#[graphql_object(context = MyContext)]
impl Query {
fn ping() -> bool {
true
}
}

struct Mutation;

#[graphql_object(context = MyContext)]
impl Mutation {
fn pong() -> bool {
true
}
}

type BoolStream = Pin<Box<dyn futures::Stream<Item = bool> + Send>>;

struct Subscription;

#[graphql_subscription(context = MyContext)]
impl Subscription {
async fn tick() -> BoolStream {
Box::pin(stream::once(async { true }))
}
}

type Schema = RootNode<Query, Mutation, Subscription, DefaultScalarValue>;

fn schema() -> Schema {
RootNode::new(Query, Mutation, Subscription)
}

#[test]
fn reports_query_operation_type() {
let result = crate::parse_and_validate("{ ping }", None, &schema(), &graphql::vars! {});
assert_eq!(result.unwrap(), OperationType::Query);
}

#[test]
fn reports_mutation_operation_type() {
let result =
crate::parse_and_validate("mutation { pong }", None, &schema(), &graphql::vars! {});
assert_eq!(result.unwrap(), OperationType::Mutation);
}

#[test]
fn reports_subscription_operation_type() {
let result =
crate::parse_and_validate("subscription { tick }", None, &schema(), &graphql::vars! {});
assert_eq!(result.unwrap(), OperationType::Subscription);
}

#[test]
fn selects_named_operation() {
let query = "query A { ping } mutation B { pong }";
let result = crate::parse_and_validate(query, Some("B"), &schema(), &graphql::vars! {});
assert_eq!(result.unwrap(), OperationType::Mutation);
}

#[test]
fn multiple_operations_without_a_name_errors() {
let query = "query A { ping } mutation B { pong }";
let result = crate::parse_and_validate(query, None, &schema(), &graphql::vars! {});
assert!(
matches!(result, Err(GraphQLError::MultipleOperationsProvided)),
"got {result:?}",
);
}

#[test]
fn validation_errors_are_reported() {
let result = crate::parse_and_validate("{ unknownField }", None, &schema(), &graphql::vars! {});
assert!(
matches!(result, Err(GraphQLError::ValidationError(_))),
"got {result:?}",
);
}

#[test]
fn parse_errors_are_reported() {
let result = crate::parse_and_validate("{ ping ", None, &schema(), &graphql::vars! {});
assert!(
matches!(result, Err(GraphQLError::ParseError(_))),
"got {result:?}",
);
}
Loading