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 @@ -21,6 +21,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Changed `ScalarToken::String` to contain raw quoted and escaped `StringLiteral` (was unquoted but escaped string before). ([#1349])
- Added `LexerError::UnterminatedBlockString` variant. ([#1349])
- Fixed `ValuesStream` to return batch of `ExecutionError`s instead of a single one. ([#1371])
- Added `GraphQLError::NotSupported` variant. ([#1378])

### Added

Expand Down Expand Up @@ -65,6 +66,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Incorrect double escaping in `ScalarToken::String` `Display`ing. ([#1349])
- Memory leak caused by incorrect error handling in `#[graphql_subscription]` macro expansion. ([#1371])
- Incorrect rejection of default values on non-`Null` variables. ([#1376])
- Executing a `mutation` against a schema without a mutation type (e.g. `EmptyMutation`), or a `subscription` against one without a subscription type (e.g. `EmptySubscription`), now returns a `GraphQLError::NotSupported` error rather than panicking. ([#1378])

[#864]: /../../issues/864
[#1055]: /../../issues/1055
Expand All @@ -80,6 +82,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1371]: /../../pull/1371
[#1376]: /../../pull/1376
[#1377]: /../../pull/1377
[#1378]: /../../pull/1378
[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
32 changes: 20 additions & 12 deletions juniper/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,10 +859,13 @@ where

let root_type = match operation.item.operation_type {
OperationType::Query => root_node.schema.query_type(),
OperationType::Mutation => root_node
.schema
.mutation_type()
.expect("No mutation type found"),
OperationType::Mutation => match root_node.schema.mutation_type() {
Some(root_type) => root_type,
// The schema has no mutation type (e.g. `EmptyMutation`), so a
// mutation operation can't be executed against it. Return a
// request error instead of panicking.
None => return Err(GraphQLError::NotSupported(OperationType::Mutation)),
},
OperationType::Subscription => unreachable!(),
};

Expand Down Expand Up @@ -957,10 +960,13 @@ where

let root_type = match operation.item.operation_type {
OperationType::Query => root_node.schema.query_type(),
OperationType::Mutation => root_node
.schema
.mutation_type()
.expect("No mutation type found"),
OperationType::Mutation => match root_node.schema.mutation_type() {
Some(root_type) => root_type,
// The schema has no mutation type (e.g. `EmptyMutation`), so a
// mutation operation can't be executed against it. Return a
// request error instead of panicking.
None => return Err(GraphQLError::NotSupported(OperationType::Mutation)),
},
OperationType::Subscription => unreachable!(),
};

Expand Down Expand Up @@ -1103,10 +1109,12 @@ where
}

let root_type = match operation.item.operation_type {
OperationType::Subscription => root_node
.schema
.subscription_type()
.expect("No subscription type found"),
OperationType::Subscription => match root_node.schema.subscription_type() {
Some(root_type) => root_type,
// The schema has no subscription type (e.g. `EmptySubscription`);
// return a request error instead of panicking.
None => return Err(GraphQLError::NotSupported(OperationType::Subscription)),
},
_ => unreachable!(),
};

Expand Down
10 changes: 9 additions & 1 deletion juniper/src/integrations/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{

use crate::{
DefaultScalarValue, GraphQLError, Object, Value,
ast::InputValue,
ast::{InputValue, OperationType},
executor::ExecutionError,
parser::{ParseError, SourcePosition, Spanning},
validation::RuleError,
Expand Down Expand Up @@ -69,6 +69,14 @@ impl Serialize for GraphQLError {
message: "Expected subscription, got query",
}]
.serialize(ser),
Self::NotSupported(op) => [Helper {
message: match op {
OperationType::Query => "Schema is not configured for queries",
OperationType::Mutation => "Schema is not configured for mutations",
OperationType::Subscription => "Schema is not configured for subscriptions",
},
}]
.serialize(ser),
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ pub enum GraphQLError {
IsSubscription,
#[display("Operation is not a subscription")]
NotSubscription,
/// The requested operation type has no corresponding root type defined in
/// the schema — e.g. a `mutation` against [`EmptyMutation`], or a
/// `subscription` against [`EmptySubscription`].
///
/// [`EmptyMutation`]: crate::EmptyMutation
/// [`EmptySubscription`]: crate::EmptySubscription
#[display("Schema is not configured for {} operations", match _0 {
OperationType::Query => "queries",
OperationType::Mutation => "mutations",
OperationType::Subscription => "subscriptions",
})]
NotSupported(OperationType),
}

impl From<RuleError> for GraphQLError {
Expand All @@ -154,7 +166,8 @@ impl std::error::Error for GraphQLError {
| Self::MultipleOperationsProvided
| Self::UnknownOperationName
| Self::IsSubscription
| Self::NotSubscription => None,
| Self::NotSubscription
| Self::NotSupported(_) => None,
}
}
}
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 @@ -4,6 +4,8 @@ pub mod fixtures;
#[cfg(test)]
mod introspection_tests;
#[cfg(test)]
mod operation_not_supported;
#[cfg(test)]
mod query_tests;
#[cfg(test)]
mod schema_introspection;
Expand Down
82 changes: 82 additions & 0 deletions juniper/src/tests/operation_not_supported.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Regression tests for [`GraphQLError::NotSupported`].
//!
//! Executing an operation whose root type the schema doesn't define — e.g. a
//! `mutation` against [`EmptyMutation`], or a `subscription` against
//! [`EmptySubscription`] — must return a request error, not panic.
use crate::{
GraphQLError,
ast::OperationType,
graphql,
schema::model::RootNode,
tests::fixtures::starwars::schema::{Database, Query},
types::scalars::{EmptyMutation, EmptySubscription},
};

fn query_only_schema() -> RootNode<Query, EmptyMutation<Database>, EmptySubscription<Database>> {
RootNode::new(
Query,
EmptyMutation::<Database>::new(),
EmptySubscription::<Database>::new(),
)
}

#[test]
fn sync_mutation_on_query_only_schema_errors_instead_of_panicking() {
let schema = query_only_schema();
let database = Database::new();
let result = crate::execute_sync(
"mutation { foo }",
None,
&schema,
&graphql::vars! {},
&database,
);
assert!(
matches!(
&result,
Err(GraphQLError::NotSupported(OperationType::Mutation)),
),
"expected NotSupported(Mutation), got {result:?}",
);
}

#[tokio::test]
async fn async_mutation_on_query_only_schema_errors_instead_of_panicking() {
let schema = query_only_schema();
let database = Database::new();
let result = crate::execute(
"mutation { foo }",
None,
&schema,
&graphql::vars! {},
&database,
)
.await;
assert!(
matches!(
&result,
Err(GraphQLError::NotSupported(OperationType::Mutation)),
),
"expected NotSupported(Mutation), got {result:?}",
);
}

#[tokio::test]
async fn subscription_without_subscription_root_errors_instead_of_panicking() {
let schema = query_only_schema();
let database = Database::new();
let result = crate::resolve_into_stream(
"subscription { foo }",
None,
&schema,
&graphql::vars! {},
&database,
)
.await;
match result {
Err(GraphQLError::NotSupported(OperationType::Subscription)) => {}
Err(other) => panic!("expected NotSupported(Subscription), got {other:?}"),
Ok(_) => panic!("expected an error, got a stream"),
}
}
Loading