Skip to main content

graphql_tools/validation/rules/
leaf_field_selections.rs

1use super::ValidationRule;
2use crate::{
3    ast::{OperationVisitor, OperationVisitorContext},
4    validation::utils::{ValidationError, ValidationErrorContext},
5};
6
7/// Leaf Field Selections
8///
9/// Field selections on scalars or enums are never allowed, because they are the leaf nodes of any GraphQL operation.
10///
11/// https://spec.graphql.org/draft/#sec-Leaf-Field-Selections
12pub struct LeafFieldSelections;
13
14impl Default for LeafFieldSelections {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl LeafFieldSelections {
21    pub fn new() -> Self {
22        LeafFieldSelections
23    }
24}
25
26impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for LeafFieldSelections {
27    fn enter_field(
28        &mut self,
29        visitor_context: &mut OperationVisitorContext,
30        user_context: &mut ValidationErrorContext,
31        field: &crate::static_graphql::query::Field,
32    ) {
33        if let (Some(field_type), Some(field_type_literal)) = (
34            (visitor_context.current_type()),
35            (visitor_context.current_type_literal()),
36        ) {
37            let field_selection_count = field.selection_set.items.len();
38
39            if field_type.is_leaf_type() {
40                if field_selection_count > 0 {
41                    user_context.report_error(ValidationError {
42                        error_code: self.error_code(),
43                        locations: vec![field.position],
44                        message: format!(
45                  "Field \"{}\" must not have a selection since type \"{}\" has no subfields.",
46                  field.name,
47                  field_type_literal
48              ),
49                    });
50                }
51            } else if field_selection_count == 0 {
52                user_context.report_error(ValidationError {error_code: self.error_code(),
53                locations: vec![field.position],
54                message: format!(
55                    "Field \"{}\" of type \"{}\" must have a selection of subfields. Did you mean \"{} {{ ... }}\"?",
56                    field.name,
57                    field_type_literal,
58                    field.name
59                ),
60            });
61            }
62        }
63    }
64}
65
66impl ValidationRule for LeafFieldSelections {
67    fn error_code(&self) -> &'static str {
68        "LeafFieldSelections"
69    }
70
71    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
72        Box::new(LeafFieldSelections::new())
73    }
74}
75
76#[test]
77fn valid_scalar_selection() {
78    use crate::validation::test_utils::*;
79
80    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
81    let errors = test_operation_with_schema(
82        "fragment scalarSelection on Dog {
83          barks
84        }",
85        TEST_SCHEMA,
86        &mut plan,
87    );
88
89    assert_eq!(get_messages(&errors).len(), 0);
90}
91
92#[test]
93fn valid_scalar_selection_with_args() {
94    use crate::validation::test_utils::*;
95
96    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
97    let errors = test_operation_with_schema(
98        "fragment scalarSelectionWithArgs on Dog {
99          doesKnowCommand(dogCommand: SIT)
100        }",
101        TEST_SCHEMA,
102        &mut plan,
103    );
104
105    assert_eq!(get_messages(&errors).len(), 0);
106}
107
108#[test]
109fn object_type_missing_selection() {
110    use crate::validation::test_utils::*;
111
112    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
113    let errors = test_operation_with_schema(
114        "query directQueryOnObjectWithoutSubFields {
115          human
116        }",
117        TEST_SCHEMA,
118        &mut plan,
119    );
120
121    let messages = get_messages(&errors);
122    assert_eq!(messages.len(), 1);
123    assert_eq!(
124        messages,
125        vec!["Field \"human\" of type \"Human\" must have a selection of subfields. Did you mean \"human { ... }\"?"]
126    );
127}
128
129#[test]
130fn interface_type_missing_selection() {
131    use crate::validation::test_utils::*;
132
133    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
134    let errors = test_operation_with_schema(
135        "{
136          human { pets }
137        }",
138        TEST_SCHEMA,
139        &mut plan,
140    );
141
142    let messages = get_messages(&errors);
143    assert_eq!(messages.len(), 1);
144    assert_eq!(
145        messages,
146        vec!["Field \"pets\" of type \"[Pet]\" must have a selection of subfields. Did you mean \"pets { ... }\"?"]
147    );
148}
149
150#[test]
151fn selection_not_allowed_on_scalar() {
152    use crate::validation::test_utils::*;
153
154    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
155    let errors = test_operation_with_schema(
156        "fragment scalarSelectionsNotAllowedOnBoolean on Dog {
157          barks { sinceWhen }
158        }",
159        TEST_SCHEMA,
160        &mut plan,
161    );
162
163    let messages = get_messages(&errors);
164    assert_eq!(messages.len(), 1);
165    assert_eq!(
166        messages,
167        vec!["Field \"barks\" must not have a selection since type \"Boolean\" has no subfields."]
168    );
169}
170
171#[test]
172fn selection_not_allowed_on_enum() {
173    use crate::validation::test_utils::*;
174
175    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
176    let errors = test_operation_with_schema(
177        "fragment scalarSelectionsNotAllowedOnEnum on Cat {
178          furColor { inHexDec }
179        }",
180        TEST_SCHEMA,
181        &mut plan,
182    );
183
184    let messages = get_messages(&errors);
185    assert_eq!(messages.len(), 1);
186    assert_eq!(
187        messages,
188        vec!["Field \"furColor\" must not have a selection since type \"FurColor\" has no subfields."]
189    );
190}
191
192#[test]
193fn scalar_selection_not_allowed_with_args() {
194    use crate::validation::test_utils::*;
195
196    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
197    let errors = test_operation_with_schema(
198        "fragment scalarSelectionsNotAllowedWithArgs on Dog {
199          doesKnowCommand(dogCommand: SIT) { sinceWhen }
200        }",
201        TEST_SCHEMA,
202        &mut plan,
203    );
204
205    let messages = get_messages(&errors);
206    assert_eq!(messages.len(), 1);
207    assert_eq!(
208        messages,
209        vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."]
210    );
211}
212
213#[test]
214fn scalar_selection_not_allowed_with_directives() {
215    use crate::validation::test_utils::*;
216
217    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
218    let errors = test_operation_with_schema(
219        "fragment scalarSelectionsNotAllowedWithDirectives on Dog {
220          name @include(if: true) { isAlsoHumanName }
221        }",
222        TEST_SCHEMA,
223        &mut plan,
224    );
225
226    let messages = get_messages(&errors);
227    assert_eq!(messages.len(), 1);
228    assert_eq!(
229        messages,
230        vec!["Field \"name\" must not have a selection since type \"String\" has no subfields."]
231    );
232}
233
234#[test]
235fn scalar_selection_not_allowed_with_directives_and_args() {
236    use crate::validation::test_utils::*;
237
238    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
239    let errors = test_operation_with_schema(
240        "fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog {
241          doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen }
242        }",
243        TEST_SCHEMA,
244        &mut plan,
245    );
246
247    let messages = get_messages(&errors);
248    assert_eq!(messages.len(), 1);
249    assert_eq!(
250        messages,
251        vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."]
252    );
253}