graphql_tools/validation/rules/
single_field_subscriptions.rs1use super::ValidationRule;
2use crate::ast::{collect_fields, visit_document, 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<'a> OperationVisitor<'a, 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<'a>(&self) -> &'a str {
92 "SingleFieldSubscriptions"
93 }
94
95 fn validate(
96 &self,
97 ctx: &mut OperationVisitorContext,
98 error_collector: &mut ValidationErrorContext,
99 ) {
100 visit_document(
101 &mut SingleFieldSubscriptions::new(),
102 ctx.operation,
103 ctx,
104 error_collector,
105 );
106 }
107}
108
109#[cfg(test)]
110pub static TEST_SCHEMA_SUBSCRIPTION: &str = "
111type Message {
112 body: String
113 sender: String
114}
115type SubscriptionRoot {
116 importantEmails: [String]
117 notImportantEmails: [String]
118 moreImportantEmails: [String]
119 spamEmails: [String]
120 deletedEmails: [String]
121 newMessage: Message
122}
123type QueryRoot {
124 dummy: String
125}
126schema {
127 query: QueryRoot
128 subscription: SubscriptionRoot
129}
130";
131
132#[test]
133fn valid_subscription_with_fragment() {
134 use crate::validation::test_utils::*;
135
136 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
137 let errors = test_operation_with_schema(
138 "subscription sub {
139 ...newMessageFields
140 }
141 fragment newMessageFields on SubscriptionRoot {
142 newMessage {
143 body
144 sender
145 }
146 }",
147 TEST_SCHEMA_SUBSCRIPTION,
148 &mut plan,
149 );
150
151 assert_eq!(get_messages(&errors).len(), 0);
152}
153
154#[test]
155fn valid_subscription_with_fragment_and_field() {
156 use crate::validation::test_utils::*;
157
158 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
159 let errors = test_operation_with_schema(
160 "subscription sub {
161 newMessage {
162 body
163 }
164 ...newMessageFields
165 }
166 fragment newMessageFields on SubscriptionRoot {
167 newMessage {
168 body
169 sender
170 }
171 }",
172 TEST_SCHEMA_SUBSCRIPTION,
173 &mut plan,
174 );
175
176 assert_eq!(get_messages(&errors).len(), 0);
177}
178
179#[test]
180fn fails_with_more_than_one_root_field() {
181 use crate::validation::test_utils::*;
182
183 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
184 let errors = test_operation_with_schema(
185 "subscription ImportantEmails {
186 importantEmails
187 notImportantEmails
188 }",
189 TEST_SCHEMA_SUBSCRIPTION,
190 &mut plan,
191 );
192
193 let messages = get_messages(&errors);
194 assert_eq!(messages.len(), 1);
195 assert_eq!(
196 messages,
197 vec!["Subscription \"ImportantEmails\" must select only one top level field."]
198 );
199}
200
201#[test]
202fn fails_with_more_than_one_root_field_including_introspection() {
203 use crate::validation::test_utils::*;
204
205 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
206 let errors = test_operation_with_schema(
207 "subscription ImportantEmails {
208 importantEmails
209 __typename
210 }",
211 TEST_SCHEMA_SUBSCRIPTION,
212 &mut plan,
213 );
214
215 let messages = get_messages(&errors);
216 assert_eq!(messages.len(), 2);
217 assert_eq!(
218 messages,
219 vec![
220 "Subscription \"ImportantEmails\" must select only one top level field.",
221 "Subscription \"ImportantEmails\" must not select an introspection top level field."
222 ]
223 );
224}
225
226#[test]
227fn fails_with_more_than_one_root_field_including_aliased_introspection_via_fragment() {
228 use crate::validation::test_utils::*;
229
230 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
231 let errors = test_operation_with_schema(
232 "subscription ImportantEmails {
233 importantEmails
234 ...Introspection
235 }
236 fragment Introspection on SubscriptionRoot {
237 typename: __typename
238 }",
239 TEST_SCHEMA_SUBSCRIPTION,
240 &mut plan,
241 );
242
243 let messages = get_messages(&errors);
244 assert_eq!(messages.len(), 2);
245 assert_eq!(
246 messages,
247 vec![
248 "Subscription \"ImportantEmails\" must select only one top level field.",
249 "Subscription \"ImportantEmails\" must not select an introspection top level field."
250 ]
251 );
252}
253
254#[test]
255fn fails_with_many_more_than_one_root_field() {
256 use crate::validation::test_utils::*;
257
258 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
259 let errors = test_operation_with_schema(
260 "subscription ImportantEmails {
261 importantEmails
262 notImportantEmails
263 spamEmails
264 }",
265 TEST_SCHEMA_SUBSCRIPTION,
266 &mut plan,
267 );
268
269 let messages = get_messages(&errors);
270 assert_eq!(messages.len(), 1);
271 assert_eq!(
272 messages,
273 vec!["Subscription \"ImportantEmails\" must select only one top level field.",]
274 );
275}
276
277#[test]
278fn fails_with_many_more_than_one_root_field_via_fragments() {
279 use crate::validation::test_utils::*;
280
281 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
282 let errors = test_operation_with_schema(
283 "subscription ImportantEmails {
284 importantEmails
285 ... {
286 more: moreImportantEmails
287 }
288 ...NotImportantEmails
289 }
290 fragment NotImportantEmails on SubscriptionRoot {
291 notImportantEmails
292 deleted: deletedEmails
293 ...SpamEmails
294 }
295 fragment SpamEmails on SubscriptionRoot {
296 spamEmails
297 }",
298 TEST_SCHEMA_SUBSCRIPTION,
299 &mut plan,
300 );
301
302 let messages = get_messages(&errors);
303 assert_eq!(messages.len(), 1);
304 assert_eq!(
305 messages,
306 vec!["Subscription \"ImportantEmails\" must select only one top level field.",]
307 );
308}
309
310#[test]
311fn does_not_infinite_loop_on_recursive_fragments() {
312 use crate::validation::test_utils::*;
313
314 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
315 let errors = test_operation_with_schema(
316 "subscription NoInfiniteLoop {
317 ...A
318 }
319 fragment A on SubscriptionRoot {
320 ...A
321 }",
322 TEST_SCHEMA_SUBSCRIPTION,
323 &mut plan,
324 );
325
326 let messages = get_messages(&errors);
327 assert_eq!(messages.len(), 0);
328}
329
330#[test]
331fn fails_with_many_more_than_one_root_field_via_fragments_anonymous() {
332 use crate::validation::test_utils::*;
333
334 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
335 let errors = test_operation_with_schema(
336 "subscription {
337 importantEmails
338 ... {
339 more: moreImportantEmails
340 ...NotImportantEmails
341 }
342 ...NotImportantEmails
343 }
344 fragment NotImportantEmails on SubscriptionRoot {
345 notImportantEmails
346 deleted: deletedEmails
347 ... {
348 ... {
349 archivedEmails
350 }
351 }
352 ...SpamEmails
353 }
354 fragment SpamEmails on SubscriptionRoot {
355 spamEmails
356 ...NonExistentFragment
357 }",
358 TEST_SCHEMA_SUBSCRIPTION,
359 &mut plan,
360 );
361
362 let messages = get_messages(&errors);
363 assert_eq!(messages.len(), 1);
364 assert_eq!(
365 messages,
366 vec!["Anonymous Subscription must select only one top level field.",]
367 );
368}
369
370#[test]
371fn fails_with_more_than_one_root_field_in_anonymous_subscriptions() {
372 use crate::validation::test_utils::*;
373
374 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
375 let errors = test_operation_with_schema(
376 "subscription {
377 importantEmails
378 notImportantEmails
379 }",
380 TEST_SCHEMA_SUBSCRIPTION,
381 &mut plan,
382 );
383
384 let messages = get_messages(&errors);
385 assert_eq!(messages.len(), 1);
386 assert_eq!(
387 messages,
388 vec!["Anonymous Subscription must select only one top level field.",]
389 );
390}
391
392#[test]
393fn fails_with_introspection_field() {
394 use crate::validation::test_utils::*;
395
396 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
397 let errors = test_operation_with_schema(
398 "subscription ImportantEmails {
399 __typename
400 }",
401 TEST_SCHEMA_SUBSCRIPTION,
402 &mut plan,
403 );
404
405 let messages = get_messages(&errors);
406 assert_eq!(messages.len(), 1);
407 assert_eq!(
408 messages,
409 vec!["Subscription \"ImportantEmails\" must not select an introspection top level field."]
410 );
411}
412
413#[test]
414fn fails_with_introspection_field_in_anonymous_subscription() {
415 use crate::validation::test_utils::*;
416
417 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
418 let errors = test_operation_with_schema(
419 "subscription {
420 __typename
421 }",
422 TEST_SCHEMA_SUBSCRIPTION,
423 &mut plan,
424 );
425
426 let messages = get_messages(&errors);
427 assert_eq!(messages.len(), 1);
428 assert_eq!(
429 messages,
430 vec!["Anonymous Subscription must not select an introspection top level field."]
431 );
432}
433
434#[test]
435fn skips_if_not_subscription_type() {
436 use crate::validation::test_utils::*;
437
438 let mut plan = create_plan_from_rule(Box::new(SingleFieldSubscriptions {}));
439 let errors = test_operation_with_schema(
440 "subscription {
441 __typename
442 }",
443 "type Query {
444 dummy: String
445 }",
446 &mut plan,
447 );
448
449 let messages = get_messages(&errors);
450 assert_eq!(messages.len(), 0);
451}