graphql_tools/validation/rules/
lone_anonymous_operation.rs

1use super::ValidationRule;
2use crate::ast::{visit_document, 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<'a> OperationVisitor<'a, 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| match n {
37                Definition::Operation(OperationDefinition::SelectionSet(_)) => true,
38                Definition::Operation(OperationDefinition::Query(_)) => true,
39                Definition::Operation(OperationDefinition::Mutation(_)) => true,
40                Definition::Operation(OperationDefinition::Subscription(_)) => true,
41                _ => false,
42            })
43            .count();
44
45        for definition in &document.definitions {
46            match definition {
47                Definition::Operation(OperationDefinition::SelectionSet(_)) => {
48                    if operations_count > 1 {
49                        user_context.report_error(ValidationError {
50                            error_code: self.error_code(),
51                            message: "This anonymous operation must be the only defined operation."
52                                .to_string(),
53                            locations: vec![],
54                        })
55                    }
56                }
57                Definition::Operation(OperationDefinition::Query(query)) => {
58                    if query.name.is_none() && operations_count > 1 {
59                        user_context.report_error(ValidationError {
60                            error_code: self.error_code(),
61                            message: "This anonymous operation must be the only defined operation."
62                                .to_string(),
63                            locations: vec![query.position],
64                        })
65                    }
66                }
67                Definition::Operation(OperationDefinition::Mutation(mutation)) => {
68                    if mutation.name.is_none() && operations_count > 1 {
69                        user_context.report_error(ValidationError {
70                            error_code: self.error_code(),
71                            message: "This anonymous operation must be the only defined operation."
72                                .to_string(),
73                            locations: vec![mutation.position],
74                        })
75                    }
76                }
77                Definition::Operation(OperationDefinition::Subscription(subscription)) => {
78                    if subscription.name.is_none() && operations_count > 1 {
79                        user_context.report_error(ValidationError {
80                            error_code: self.error_code(),
81                            message: "This anonymous operation must be the only defined operation."
82                                .to_string(),
83                            locations: vec![subscription.position],
84                        })
85                    }
86                }
87                _ => {}
88            };
89        }
90    }
91}
92
93impl ValidationRule for LoneAnonymousOperation {
94    fn error_code<'a>(&self) -> &'a str {
95        "LoneAnonymousOperation"
96    }
97
98    fn validate(
99        &self,
100        ctx: &mut OperationVisitorContext,
101        error_collector: &mut ValidationErrorContext,
102    ) {
103        visit_document(
104            &mut LoneAnonymousOperation::new(),
105            ctx.operation,
106            ctx,
107            error_collector,
108        );
109    }
110}
111
112#[test]
113fn no_operations() {
114    use crate::validation::test_utils::*;
115
116    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
117    let errors = test_operation_with_schema(
118        "fragment fragA on Type {
119          field
120        }",
121        TEST_SCHEMA,
122        &mut plan,
123    );
124
125    assert_eq!(get_messages(&errors).len(), 0);
126}
127
128#[test]
129fn one_anon_operation() {
130    use crate::validation::test_utils::*;
131
132    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
133    let errors = test_operation_with_schema(
134        "{
135          field
136        }",
137        TEST_SCHEMA,
138        &mut plan,
139    );
140
141    assert_eq!(get_messages(&errors).len(), 0);
142}
143
144#[test]
145fn mutiple_named() {
146    use crate::validation::test_utils::*;
147
148    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
149    let errors = test_operation_with_schema(
150        "query Foo {
151          field
152        }
153        query Bar {
154          field
155        }",
156        TEST_SCHEMA,
157        &mut plan,
158    );
159
160    assert_eq!(get_messages(&errors).len(), 0);
161}
162
163#[test]
164fn anon_operation_with_fragment() {
165    use crate::validation::test_utils::*;
166
167    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
168    let errors = test_operation_with_schema(
169        "{
170          ...Foo
171        }
172        fragment Foo on Type {
173          field
174        }",
175        TEST_SCHEMA,
176        &mut plan,
177    );
178
179    assert_eq!(get_messages(&errors).len(), 0);
180}
181
182#[test]
183fn multiple_anon_operations() {
184    use crate::validation::test_utils::*;
185
186    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
187    let errors = test_operation_with_schema(
188        "{
189          fieldA
190        }
191        {
192          fieldB
193        }",
194        TEST_SCHEMA,
195        &mut plan,
196    );
197
198    let messages = get_messages(&errors);
199    assert_eq!(messages.len(), 2);
200    assert_eq!(
201        messages,
202        vec![
203            "This anonymous operation must be the only defined operation.",
204            "This anonymous operation must be the only defined operation."
205        ]
206    );
207}
208
209#[test]
210fn anon_operation_with_mutation() {
211    use crate::validation::test_utils::*;
212
213    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
214    let errors = test_operation_with_schema(
215        "{
216          fieldA
217        }
218        mutation Foo {
219          fieldB
220        }",
221        TEST_SCHEMA,
222        &mut plan,
223    );
224
225    let messages = get_messages(&errors);
226    assert_eq!(messages.len(), 1);
227    assert_eq!(
228        messages,
229        vec!["This anonymous operation must be the only defined operation."]
230    );
231}
232
233#[test]
234fn anon_operation_with_subscription() {
235    use crate::validation::test_utils::*;
236
237    let mut plan = create_plan_from_rule(Box::new(LoneAnonymousOperation {}));
238    let errors = test_operation_with_schema(
239        "{
240          fieldA
241        }
242        subscription Foo {
243          fieldB
244        }",
245        TEST_SCHEMA,
246        &mut plan,
247    );
248
249    let messages = get_messages(&errors);
250    assert_eq!(messages.len(), 1);
251    assert_eq!(
252        messages,
253        vec!["This anonymous operation must be the only defined operation."]
254    );
255}