Skip to main content

graphql_tools/validation/rules/
single_field_subscriptions.rs

1use 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
7/// Unique operation names
8///
9/// A GraphQL document is only valid if all defined operations have unique names.
10///
11/// See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
12pub 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}