graphql_tools/validation/rules/
single_field_subscriptions.rs1use super::ValidationRule;
2use crate::ast::{collect_fields, OperationVisitor, OperationVisitorContext};
3use crate::static_graphql::query::OperationDefinition;
4use crate::static_graphql::schema::TypeDefinition;
5use crate::validation::utils::{ValidationError, ValidationErrorContext};
6
7pub struct SingleFieldSubscriptions;
13
14impl Default for SingleFieldSubscriptions {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20impl SingleFieldSubscriptions {
21 pub fn new() -> Self {
22 Self
23 }
24}
25
26impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for SingleFieldSubscriptions {
27 fn enter_operation_definition(
28 &mut self,
29 visitor_context: &mut OperationVisitorContext,
30 user_context: &mut ValidationErrorContext,
31 operation: &OperationDefinition,
32 ) {
33 if let OperationDefinition::Subscription(subscription) = operation {
34 if let Some(subscription_type) = visitor_context.schema.subscription_type() {
35 let operation_name = subscription.name.as_ref();
36
37 let selection_set_fields = collect_fields(
38 &subscription.selection_set,
39 &TypeDefinition::Object(subscription_type.clone()),
40 &visitor_context.known_fragments,
41 visitor_context,
42 );
43
44 if selection_set_fields.len() > 1 {
45 let error_message = match operation_name {
46 Some(operation_name) => format!(
47 "Subscription \"{}\" must select only one top level field.",
48 operation_name
49 ),
50 None => "Anonymous Subscription must select only one top level field."
51 .to_owned(),
52 };
53
54 user_context.report_error(ValidationError {
55 error_code: self.error_code(),
56 locations: vec![subscription.position],
57 message: error_message,
58 });
59 }
60
61 selection_set_fields
62 .into_iter()
63 .filter_map(|(field_name, fields_records)| {
64 if field_name.starts_with("__") {
65 return Some((field_name, fields_records));
66 }
67
68 None
69 })
70 .for_each(|(_field_name, _fields_records)| {
71 let error_message = match operation_name {
72 Some(operation_name) => format!(
73 "Subscription \"{}\" must not select an introspection top level field.",
74 operation_name
75 ),
76 None => "Anonymous Subscription must not select an introspection top level field."
77 .to_owned(),
78 };
79
80 user_context.report_error(ValidationError {error_code: self.error_code(),
81 locations: vec![subscription.position],
82 message: error_message,
83 });
84 })
85 }
86 }
87 }
88}
89
90impl ValidationRule for SingleFieldSubscriptions {
91 fn error_code(&self) -> &'static str {
92 "SingleFieldSubscriptions"
93 }
94
95 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
96 Box::new(SingleFieldSubscriptions::new())
97 }
98}
99
100#[cfg(test)]
101pub static TEST_SCHEMA_SUBSCRIPTION: &str = "
102type Message {
103 body: String
104 sender: String
105}
106type SubscriptionRoot {
107 importantEmails: [String]
108 notImportantEmails: [String]
109 moreImportantEmails: [String]
110 spamEmails: [String]
111 deletedEmails: [String]
112 newMessage: Message
113}
114type QueryRoot {
115 dummy: String
116}
117schema {
118 query: QueryRoot
119 subscription: SubscriptionRoot
120}
121";
122
123#[test]
124fn valid_subscription_with_fragment() {
125 use crate::validation::test_utils::*;
126
127 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
128 let errors = test_operation_with_schema(
129 "subscription sub {
130 ...newMessageFields
131 }
132 fragment newMessageFields on SubscriptionRoot {
133 newMessage {
134 body
135 sender
136 }
137 }",
138 TEST_SCHEMA_SUBSCRIPTION,
139 &mut plan,
140 );
141
142 assert_eq!(get_messages(&errors).len(), 0);
143}
144
145#[test]
146fn valid_subscription_with_fragment_and_field() {
147 use crate::validation::test_utils::*;
148
149 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
150 let errors = test_operation_with_schema(
151 "subscription sub {
152 newMessage {
153 body
154 }
155 ...newMessageFields
156 }
157 fragment newMessageFields on SubscriptionRoot {
158 newMessage {
159 body
160 sender
161 }
162 }",
163 TEST_SCHEMA_SUBSCRIPTION,
164 &mut plan,
165 );
166
167 assert_eq!(get_messages(&errors).len(), 0);
168}
169
170#[test]
171fn fails_with_more_than_one_root_field() {
172 use crate::validation::test_utils::*;
173
174 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
175 let errors = test_operation_with_schema(
176 "subscription ImportantEmails {
177 importantEmails
178 notImportantEmails
179 }",
180 TEST_SCHEMA_SUBSCRIPTION,
181 &mut plan,
182 );
183
184 let messages = get_messages(&errors);
185 assert_eq!(messages.len(), 1);
186 assert_eq!(
187 messages,
188 vec!["Subscription \"ImportantEmails\" must select only one top level field."]
189 );
190}
191
192#[test]
193fn fails_with_more_than_one_root_field_including_introspection() {
194 use crate::validation::test_utils::*;
195
196 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
197 let errors = test_operation_with_schema(
198 "subscription ImportantEmails {
199 importantEmails
200 __typename
201 }",
202 TEST_SCHEMA_SUBSCRIPTION,
203 &mut plan,
204 );
205
206 let messages = get_messages(&errors);
207 assert_eq!(messages.len(), 2);
208 assert_eq!(
209 messages,
210 vec![
211 "Subscription \"ImportantEmails\" must select only one top level field.",
212 "Subscription \"ImportantEmails\" must not select an introspection top level field."
213 ]
214 );
215}
216
217#[test]
218fn fails_with_more_than_one_root_field_including_aliased_introspection_via_fragment() {
219 use crate::validation::test_utils::*;
220
221 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
222 let errors = test_operation_with_schema(
223 "subscription ImportantEmails {
224 importantEmails
225 ...Introspection
226 }
227 fragment Introspection on SubscriptionRoot {
228 typename: __typename
229 }",
230 TEST_SCHEMA_SUBSCRIPTION,
231 &mut plan,
232 );
233
234 let messages = get_messages(&errors);
235 assert_eq!(messages.len(), 2);
236 assert_eq!(
237 messages,
238 vec![
239 "Subscription \"ImportantEmails\" must select only one top level field.",
240 "Subscription \"ImportantEmails\" must not select an introspection top level field."
241 ]
242 );
243}
244
245#[test]
246fn fails_with_many_more_than_one_root_field() {
247 use crate::validation::test_utils::*;
248
249 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
250 let errors = test_operation_with_schema(
251 "subscription ImportantEmails {
252 importantEmails
253 notImportantEmails
254 spamEmails
255 }",
256 TEST_SCHEMA_SUBSCRIPTION,
257 &mut plan,
258 );
259
260 let messages = get_messages(&errors);
261 assert_eq!(messages.len(), 1);
262 assert_eq!(
263 messages,
264 vec!["Subscription \"ImportantEmails\" must select only one top level field.",]
265 );
266}
267
268#[test]
269fn fails_with_many_more_than_one_root_field_via_fragments() {
270 use crate::validation::test_utils::*;
271
272 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
273 let errors = test_operation_with_schema(
274 "subscription ImportantEmails {
275 importantEmails
276 ... {
277 more: moreImportantEmails
278 }
279 ...NotImportantEmails
280 }
281 fragment NotImportantEmails on SubscriptionRoot {
282 notImportantEmails
283 deleted: deletedEmails
284 ...SpamEmails
285 }
286 fragment SpamEmails on SubscriptionRoot {
287 spamEmails
288 }",
289 TEST_SCHEMA_SUBSCRIPTION,
290 &mut plan,
291 );
292
293 let messages = get_messages(&errors);
294 assert_eq!(messages.len(), 1);
295 assert_eq!(
296 messages,
297 vec!["Subscription \"ImportantEmails\" must select only one top level field.",]
298 );
299}
300
301#[test]
302fn does_not_infinite_loop_on_recursive_fragments() {
303 use crate::validation::test_utils::*;
304
305 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
306 let errors = test_operation_with_schema(
307 "subscription NoInfiniteLoop {
308 ...A
309 }
310 fragment A on SubscriptionRoot {
311 ...A
312 }",
313 TEST_SCHEMA_SUBSCRIPTION,
314 &mut plan,
315 );
316
317 let messages = get_messages(&errors);
318 assert_eq!(messages.len(), 0);
319}
320
321#[test]
322fn fails_with_many_more_than_one_root_field_via_fragments_anonymous() {
323 use crate::validation::test_utils::*;
324
325 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
326 let errors = test_operation_with_schema(
327 "subscription {
328 importantEmails
329 ... {
330 more: moreImportantEmails
331 ...NotImportantEmails
332 }
333 ...NotImportantEmails
334 }
335 fragment NotImportantEmails on SubscriptionRoot {
336 notImportantEmails
337 deleted: deletedEmails
338 ... {
339 ... {
340 archivedEmails
341 }
342 }
343 ...SpamEmails
344 }
345 fragment SpamEmails on SubscriptionRoot {
346 spamEmails
347 ...NonExistentFragment
348 }",
349 TEST_SCHEMA_SUBSCRIPTION,
350 &mut plan,
351 );
352
353 let messages = get_messages(&errors);
354 assert_eq!(messages.len(), 1);
355 assert_eq!(
356 messages,
357 vec!["Anonymous Subscription must select only one top level field.",]
358 );
359}
360
361#[test]
362fn fails_with_more_than_one_root_field_in_anonymous_subscriptions() {
363 use crate::validation::test_utils::*;
364
365 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
366 let errors = test_operation_with_schema(
367 "subscription {
368 importantEmails
369 notImportantEmails
370 }",
371 TEST_SCHEMA_SUBSCRIPTION,
372 &mut plan,
373 );
374
375 let messages = get_messages(&errors);
376 assert_eq!(messages.len(), 1);
377 assert_eq!(
378 messages,
379 vec!["Anonymous Subscription must select only one top level field.",]
380 );
381}
382
383#[test]
384fn fails_with_introspection_field() {
385 use crate::validation::test_utils::*;
386
387 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
388 let errors = test_operation_with_schema(
389 "subscription ImportantEmails {
390 __typename
391 }",
392 TEST_SCHEMA_SUBSCRIPTION,
393 &mut plan,
394 );
395
396 let messages = get_messages(&errors);
397 assert_eq!(messages.len(), 1);
398 assert_eq!(
399 messages,
400 vec!["Subscription \"ImportantEmails\" must not select an introspection top level field."]
401 );
402}
403
404#[test]
405fn fails_with_introspection_field_in_anonymous_subscription() {
406 use crate::validation::test_utils::*;
407
408 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
409 let errors = test_operation_with_schema(
410 "subscription {
411 __typename
412 }",
413 TEST_SCHEMA_SUBSCRIPTION,
414 &mut plan,
415 );
416
417 let messages = get_messages(&errors);
418 assert_eq!(messages.len(), 1);
419 assert_eq!(
420 messages,
421 vec!["Anonymous Subscription must not select an introspection top level field."]
422 );
423}
424
425#[test]
426fn skips_if_not_subscription_type() {
427 use crate::validation::test_utils::*;
428
429 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
430 let errors = test_operation_with_schema(
431 "subscription {
432 __typename
433 }",
434 "type Query {
435 dummy: String
436 }",
437 &mut plan,
438 );
439
440 let messages = get_messages(&errors);
441 assert_eq!(messages.len(), 0);
442}