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