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