parent
09da50b143
commit
e264cf509d
4 changed files with 86 additions and 28 deletions
|
@ -1,14 +1,12 @@
|
||||||
//! Checks that `__typename` field queries okay on root types.
|
//! Checks that `__typename` field queries okay (and not okay) on root types.
|
||||||
//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details.
|
//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details.
|
||||||
|
|
||||||
use futures::{stream, FutureExt as _};
|
use futures::stream;
|
||||||
use juniper::{
|
use juniper::{
|
||||||
execute, graphql_object, graphql_subscription, graphql_value, graphql_vars,
|
execute, graphql_object, graphql_subscription, graphql_value, graphql_vars,
|
||||||
resolve_into_stream, RootNode,
|
resolve_into_stream, GraphQLError, RootNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::extract_next;
|
|
||||||
|
|
||||||
pub struct Query;
|
pub struct Query;
|
||||||
|
|
||||||
#[graphql_object]
|
#[graphql_object]
|
||||||
|
@ -102,12 +100,23 @@ async fn subscription_typename() {
|
||||||
|
|
||||||
let schema = RootNode::new(Query, Mutation, Subscription);
|
let schema = RootNode::new(Query, Mutation, Subscription);
|
||||||
|
|
||||||
assert_eq!(
|
match resolve_into_stream(query, None, &schema, &graphql_vars! {}, &()).await {
|
||||||
resolve_into_stream(query, None, &schema, &graphql_vars! {}, &())
|
Err(GraphQLError::ValidationError(mut errors)) => {
|
||||||
.then(|s| extract_next(s))
|
assert_eq!(errors.len(), 1);
|
||||||
.await,
|
|
||||||
Ok((graphql_value!({"__typename": "Subscription"}), vec![])),
|
let err = errors.pop().unwrap();
|
||||||
);
|
|
||||||
|
assert_eq!(
|
||||||
|
err.message(),
|
||||||
|
"`__typename` may not be included as a root field in a \
|
||||||
|
subscription operation",
|
||||||
|
);
|
||||||
|
assert_eq!(err.locations()[0].index(), 15);
|
||||||
|
assert_eq!(err.locations()[0].line(), 0);
|
||||||
|
assert_eq!(err.locations()[0].column(), 15);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected ValidationError"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -116,10 +125,21 @@ async fn explicit_subscription_typename() {
|
||||||
|
|
||||||
let schema = RootNode::new(Query, Mutation, Subscription);
|
let schema = RootNode::new(Query, Mutation, Subscription);
|
||||||
|
|
||||||
assert_eq!(
|
match resolve_into_stream(query, None, &schema, &graphql_vars! {}, &()).await {
|
||||||
resolve_into_stream(query, None, &schema, &graphql_vars! {}, &())
|
Err(GraphQLError::ValidationError(mut errors)) => {
|
||||||
.then(|s| extract_next(s))
|
assert_eq!(errors.len(), 1);
|
||||||
.await,
|
|
||||||
Ok((graphql_value!({"__typename": "Subscription"}), vec![])),
|
let err = errors.pop().unwrap();
|
||||||
);
|
|
||||||
|
assert_eq!(
|
||||||
|
err.message(),
|
||||||
|
"`__typename` may not be included as a root field in a \
|
||||||
|
subscription operation"
|
||||||
|
);
|
||||||
|
assert_eq!(err.locations()[0].index(), 28);
|
||||||
|
assert_eq!(err.locations()[0].line(), 0);
|
||||||
|
assert_eq!(err.locations()[0].column(), 28);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected ValidationError"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
- `#[graphql_object]` and `#[graphql_subscription]` macros expansion now preserves defined `impl` blocks "as is" and reuses defined methods in opaque way. ([#971](https://github.com/graphql-rust/juniper/pull/971))
|
- `#[graphql_object]` and `#[graphql_subscription]` macros expansion now preserves defined `impl` blocks "as is" and reuses defined methods in opaque way. ([#971](https://github.com/graphql-rust/juniper/pull/971))
|
||||||
- `rename = "<policy>"` attribute's argument renamed to `rename_all = "<policy>"`. ([#971](https://github.com/graphql-rust/juniper/pull/971))
|
- `rename = "<policy>"` attribute's argument renamed to `rename_all = "<policy>"`. ([#971](https://github.com/graphql-rust/juniper/pull/971))
|
||||||
- Upgrade `bson` feature to [2.0 version of its crate](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979](https://github.com/graphql-rust/juniper/pull/979))
|
- Upgrade `bson` feature to [2.0 version of its crate](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979](https://github.com/graphql-rust/juniper/pull/979))
|
||||||
|
- Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use futures::{future, stream};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -293,16 +292,6 @@ where
|
||||||
|
|
||||||
let response_name = f.alias.as_ref().unwrap_or(&f.name).item;
|
let response_name = f.alias.as_ref().unwrap_or(&f.name).item;
|
||||||
|
|
||||||
if f.name.item == "__typename" {
|
|
||||||
let typename =
|
|
||||||
Value::scalar(instance.concrete_type_name(executor.context(), info));
|
|
||||||
object.add_field(
|
|
||||||
response_name,
|
|
||||||
Value::Scalar(Box::pin(stream::once(future::ok(typename)))),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let meta_field = meta_type
|
let meta_field = meta_type
|
||||||
.field_by_name(f.name.item)
|
.field_by_name(f.name.item)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
validation::{ValidatorContext, Visitor},
|
validation::{ValidatorContext, Visitor},
|
||||||
value::ScalarValue,
|
value::ScalarValue,
|
||||||
|
Operation, OperationType, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FieldsOnCorrectType;
|
pub struct FieldsOnCorrectType;
|
||||||
|
@ -16,6 +17,27 @@ impl<'a, S> Visitor<'a, S> for FieldsOnCorrectType
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
|
fn enter_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
context: &mut ValidatorContext<'a, S>,
|
||||||
|
operation: &'a Spanning<Operation<S>>,
|
||||||
|
) {
|
||||||
|
// https://spec.graphql.org/October2021/#note-bc213
|
||||||
|
if let OperationType::Subscription = operation.item.operation_type {
|
||||||
|
for selection in &operation.item.selection_set {
|
||||||
|
if let Selection::Field(field) = selection {
|
||||||
|
if field.item.name.item == "__typename" {
|
||||||
|
context.report_error(
|
||||||
|
"`__typename` may not be included as a root \
|
||||||
|
field in a subscription operation",
|
||||||
|
&[field.item.name.start],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn enter_field(
|
fn enter_field(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut ValidatorContext<'a, S>,
|
context: &mut ValidatorContext<'a, S>,
|
||||||
|
@ -357,4 +379,30 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forbids_typename_on_subscription() {
|
||||||
|
expect_fails_rule::<_, _, DefaultScalarValue>(
|
||||||
|
factory,
|
||||||
|
r#"subscription { __typename }"#,
|
||||||
|
&[RuleError::new(
|
||||||
|
"`__typename` may not be included as a root field in a \
|
||||||
|
subscription operation",
|
||||||
|
&[SourcePosition::new(15, 0, 15)],
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forbids_typename_on_explicit_subscription() {
|
||||||
|
expect_fails_rule::<_, _, DefaultScalarValue>(
|
||||||
|
factory,
|
||||||
|
r#"subscription SubscriptionRoot { __typename }"#,
|
||||||
|
&[RuleError::new(
|
||||||
|
"`__typename` may not be included as a root field in a \
|
||||||
|
subscription operation",
|
||||||
|
&[SourcePosition::new(32, 0, 32)],
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue