Skip to content

Commit e264cf5

Browse files
authored
Forbid __typename on subscription root (#1001, #1000)
1 parent 09da50b commit e264cf5

File tree

4 files changed

+86
-28
lines changed

4 files changed

+86
-28
lines changed

integration_tests/juniper_tests/src/issue_372.rs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
//! Checks that `__typename` field queries okay on root types.
1+
//! Checks that `__typename` field queries okay (and not okay) on root types.
22
//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details.
33
4-
use futures::{stream, FutureExt as _};
4+
use futures::stream;
55
use juniper::{
66
execute, graphql_object, graphql_subscription, graphql_value, graphql_vars,
7-
resolve_into_stream, RootNode,
7+
resolve_into_stream, GraphQLError, RootNode,
88
};
99

10-
use crate::util::extract_next;
11-
1210
pub struct Query;
1311

1412
#[graphql_object]
@@ -102,12 +100,23 @@ async fn subscription_typename() {
102100

103101
let schema = RootNode::new(Query, Mutation, Subscription);
104102

105-
assert_eq!(
106-
resolve_into_stream(query, None, &schema, &graphql_vars! {}, &())
107-
.then(|s| extract_next(s))
108-
.await,
109-
Ok((graphql_value!({"__typename": "Subscription"}), vec![])),
110-
);
103+
match resolve_into_stream(query, None, &schema, &graphql_vars! {}, &()).await {
104+
Err(GraphQLError::ValidationError(mut errors)) => {
105+
assert_eq!(errors.len(), 1);
106+
107+
let err = errors.pop().unwrap();
108+
109+
assert_eq!(
110+
err.message(),
111+
"`__typename` may not be included as a root field in a \
112+
subscription operation",
113+
);
114+
assert_eq!(err.locations()[0].index(), 15);
115+
assert_eq!(err.locations()[0].line(), 0);
116+
assert_eq!(err.locations()[0].column(), 15);
117+
}
118+
_ => panic!("Expected ValidationError"),
119+
};
111120
}
112121

113122
#[tokio::test]
@@ -116,10 +125,21 @@ async fn explicit_subscription_typename() {
116125

117126
let schema = RootNode::new(Query, Mutation, Subscription);
118127

119-
assert_eq!(
120-
resolve_into_stream(query, None, &schema, &graphql_vars! {}, &())
121-
.then(|s| extract_next(s))
122-
.await,
123-
Ok((graphql_value!({"__typename": "Subscription"}), vec![])),
124-
);
128+
match resolve_into_stream(query, None, &schema, &graphql_vars! {}, &()).await {
129+
Err(GraphQLError::ValidationError(mut errors)) => {
130+
assert_eq!(errors.len(), 1);
131+
132+
let err = errors.pop().unwrap();
133+
134+
assert_eq!(
135+
err.message(),
136+
"`__typename` may not be included as a root field in a \
137+
subscription operation"
138+
);
139+
assert_eq!(err.locations()[0].index(), 28);
140+
assert_eq!(err.locations()[0].line(), 0);
141+
assert_eq!(err.locations()[0].column(), 28);
142+
}
143+
_ => panic!("Expected ValidationError"),
144+
};
125145
}

juniper/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- `#[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))
88
- `rename = "<policy>"` attribute's argument renamed to `rename_all = "<policy>"`. ([#971](https://github.com/graphql-rust/juniper/pull/971))
99
- 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))
10+
- 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))
1011

1112
## Features
1213

juniper/src/types/subscriptions.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use futures::{future, stream};
21
use serde::Serialize;
32

43
use crate::{
@@ -293,16 +292,6 @@ where
293292

294293
let response_name = f.alias.as_ref().unwrap_or(&f.name).item;
295294

296-
if f.name.item == "__typename" {
297-
let typename =
298-
Value::scalar(instance.concrete_type_name(executor.context(), info));
299-
object.add_field(
300-
response_name,
301-
Value::Scalar(Box::pin(stream::once(future::ok(typename)))),
302-
);
303-
continue;
304-
}
305-
306295
let meta_field = meta_type
307296
.field_by_name(f.name.item)
308297
.unwrap_or_else(|| {

juniper/src/validation/rules/fields_on_correct_type.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
schema::meta::MetaType,
55
validation::{ValidatorContext, Visitor},
66
value::ScalarValue,
7+
Operation, OperationType, Selection,
78
};
89

910
pub struct FieldsOnCorrectType;
@@ -16,6 +17,27 @@ impl<'a, S> Visitor<'a, S> for FieldsOnCorrectType
1617
where
1718
S: ScalarValue,
1819
{
20+
fn enter_operation_definition(
21+
&mut self,
22+
context: &mut ValidatorContext<'a, S>,
23+
operation: &'a Spanning<Operation<S>>,
24+
) {
25+
// https://spec.graphql.org/October2021/#note-bc213
26+
if let OperationType::Subscription = operation.item.operation_type {
27+
for selection in &operation.item.selection_set {
28+
if let Selection::Field(field) = selection {
29+
if field.item.name.item == "__typename" {
30+
context.report_error(
31+
"`__typename` may not be included as a root \
32+
field in a subscription operation",
33+
&[field.item.name.start],
34+
);
35+
}
36+
}
37+
}
38+
}
39+
}
40+
1941
fn enter_field(
2042
&mut self,
2143
context: &mut ValidatorContext<'a, S>,
@@ -357,4 +379,30 @@ mod tests {
357379
"#,
358380
);
359381
}
382+
383+
#[test]
384+
fn forbids_typename_on_subscription() {
385+
expect_fails_rule::<_, _, DefaultScalarValue>(
386+
factory,
387+
r#"subscription { __typename }"#,
388+
&[RuleError::new(
389+
"`__typename` may not be included as a root field in a \
390+
subscription operation",
391+
&[SourcePosition::new(15, 0, 15)],
392+
)],
393+
);
394+
}
395+
396+
#[test]
397+
fn forbids_typename_on_explicit_subscription() {
398+
expect_fails_rule::<_, _, DefaultScalarValue>(
399+
factory,
400+
r#"subscription SubscriptionRoot { __typename }"#,
401+
&[RuleError::new(
402+
"`__typename` may not be included as a root field in a \
403+
subscription operation",
404+
&[SourcePosition::new(32, 0, 32)],
405+
)],
406+
);
407+
}
360408
}

0 commit comments

Comments
 (0)