Skip to main content

graphql_tools/validation/rules/
lone_anonymous_operation.rs

1use super::ValidationRule;
2use crate::ast::{OperationVisitor, OperationVisitorContext};
3use crate::static_graphql::query::*;
4use crate::validation::utils::{ValidationError, ValidationErrorContext};
5
6/// Lone Anonymous Operation
7///
8/// A GraphQL document is only valid if when it contains an anonymous operation
9/// (the query short-hand) that it contains only that one operation definition.
10///
11/// https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation
12pub struct LoneAnonymousOperation;
13
14impl Default for LoneAnonymousOperation {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl LoneAnonymousOperation {
21    pub fn new() -> Self {
22        LoneAnonymousOperation
23    }
24}
25
26impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for LoneAnonymousOperation {
27    fn enter_document(
28        &mut self,
29        _: &mut OperationVisitorContext,
30        user_context: &mut ValidationErrorContext,
31        document: &Document,
32    ) {
33        let operations_count = document
34            .definitions
35            .iter()
36            .filter(|n| {
37                matches!(
38                    n,
39                    Definition::Operation(OperationDefinition::SelectionSet(_))
40                        | Definition::Operation(OperationDefinition::Query(_))
41                        | Definition::Operation(OperationDefinition::Mutation(_))
42                        | Definition::Operation(OperationDefinition::Subscription(_))
43                )
44            })
45            .count();
46
47        for definition in &document.definitions {
48            match definition {
49                Definition::Operation(OperationDefinition::SelectionSet(_))
50                    if operations_count > 1 =>
51                {
52                    user_context.report_error(ValidationError {
53                        error_code: self.error_code(),
54                        message: "This anonymous operation must be the only defined operation."
55                            .to_string(),
56                        locations: vec![],
57                    })
58                }
59                Definition::Operation(OperationDefinition::Query(query))
60                    if query.name.is_none() && operations_count > 1 =>
61                {
62                    user_context.report_error(ValidationError {
63                        error_code: self.error_code(),
64                        message: "This anonymous operation must be the only defined operation."
65                            .to_string(),
66                        locations: vec![query.position],
67                    })
68                }
69                Definition::Operation(OperationDefinition::Mutation(mutation))
70                    if mutation.name.is_none() && operations_count > 1 =>
71                {
72                    user_context.report_error(ValidationError {
73                        error_code: self.error_code(),
74                        message: "This anonymous operation must be the only defined operation."
75                            .to_string(),
76                        locations: vec![mutation.position],
77                    })
78                }
79                Definition::Operation(OperationDefinition::Subscription(subscription))
80                    if subscription.name.is_none() && operations_count > 1 =>
81                {
82                    user_context.report_error(ValidationError {
83                        error_code: self.error_code(),
84                        message: "This anonymous operation must be the only defined operation."
85                            .to_string(),
86                        locations: vec![subscription.position],
87                    })
88                }
89                _ => {}
90            };
91        }
92    }
93}
94
95impl ValidationRule for LoneAnonymousOperation {
96    fn error_code(&self) -> &'static str {
97        "LoneAnonymousOperation"
98    }
99
100    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
101        Box::new(LoneAnonymousOperation::new())
102    }
103}
104
105#[test]
106fn no_operations() {
107    use crate::validation::test_utils::*;
108
109    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
110    let errors = test_operation_with_schema(
111        "fragment fragA on Type {
112          field
113        }",
114        TEST_SCHEMA,
115        &mut plan,
116    );
117
118    assert_eq!(get_messages(&errors).len(), 0);
119}
120
121#[test]
122fn one_anon_operation() {
123    use crate::validation::test_utils::*;
124
125    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
126    let errors = test_operation_with_schema(
127        "{
128          field
129        }",
130        TEST_SCHEMA,
131        &mut plan,
132    );
133
134    assert_eq!(get_messages(&errors).len(), 0);
135}
136
137#[test]
138fn mutiple_named() {
139    use crate::validation::test_utils::*;
140
141    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
142    let errors = test_operation_with_schema(
143        "query Foo {
144          field
145        }
146        query Bar {
147          field
148        }",
149        TEST_SCHEMA,
150        &mut plan,
151    );
152
153    assert_eq!(get_messages(&errors).len(), 0);
154}
155
156#[test]
157fn anon_operation_with_fragment() {
158    use crate::validation::test_utils::*;
159
160    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
161    let errors = test_operation_with_schema(
162        "{
163          ...Foo
164        }
165        fragment Foo on Type {
166          field
167        }",
168        TEST_SCHEMA,
169        &mut plan,
170    );
171
172    assert_eq!(get_messages(&errors).len(), 0);
173}
174
175#[test]
176fn multiple_anon_operations() {
177    use crate::validation::test_utils::*;
178
179    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
180    let errors = test_operation_with_schema(
181        "{
182          fieldA
183        }
184        {
185          fieldB
186        }",
187        TEST_SCHEMA,
188        &mut plan,
189    );
190
191    let messages = get_messages(&errors);
192    assert_eq!(messages.len(), 2);
193    assert_eq!(
194        messages,
195        vec![
196            "This anonymous operation must be the only defined operation.",
197            "This anonymous operation must be the only defined operation."
198        ]
199    );
200}
201
202#[test]
203fn anon_operation_with_mutation() {
204    use crate::validation::test_utils::*;
205
206    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
207    let errors = test_operation_with_schema(
208        "{
209          fieldA
210        }
211        mutation Foo {
212          fieldB
213        }",
214        TEST_SCHEMA,
215        &mut plan,
216    );
217
218    let messages = get_messages(&errors);
219    assert_eq!(messages.len(), 1);
220    assert_eq!(
221        messages,
222        vec!["This anonymous operation must be the only defined operation."]
223    );
224}
225
226#[test]
227fn anon_operation_with_subscription() {
228    use crate::validation::test_utils::*;
229
230    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
231    let errors = test_operation_with_schema(
232        "{
233          fieldA
234        }
235        subscription Foo {
236          fieldB
237        }",
238        TEST_SCHEMA,
239        &mut plan,
240    );
241
242    let messages = get_messages(&errors);
243    assert_eq!(messages.len(), 1);
244    assert_eq!(
245        messages,
246        vec!["This anonymous operation must be the only defined operation."]
247    );
248}