graphql_tools/validation/rules/
single_field_subscriptions.rs

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