Skip to main content

graphql_tools/validation/rules/
single_field_subscriptions.rs

1use 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
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<'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}