graphql_tools/validation/rules/
leaf_field_selections.rs

1use super::ValidationRule;
2use crate::{
3    ast::{visit_document, OperationVisitor, OperationVisitorContext, TypeDefinitionExtension},
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<'a> OperationVisitor<'a, 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<'a>(&self) -> &'a str {
68        "LeafFieldSelections"
69    }
70
71    fn validate(
72        &self,
73        ctx: &mut OperationVisitorContext,
74        error_collector: &mut ValidationErrorContext,
75    ) {
76        visit_document(
77            &mut LeafFieldSelections::new(),
78            ctx.operation,
79            ctx,
80            error_collector,
81        );
82    }
83}
84
85#[test]
86fn valid_scalar_selection() {
87    use crate::validation::test_utils::*;
88
89    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
90    let errors = test_operation_with_schema(
91        "fragment scalarSelection on Dog {
92          barks
93        }",
94        TEST_SCHEMA,
95        &mut plan,
96    );
97
98    assert_eq!(get_messages(&errors).len(), 0);
99}
100
101#[test]
102fn object_type_missing_selection() {
103    use crate::validation::test_utils::*;
104
105    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
106    let errors = test_operation_with_schema(
107        "query directQueryOnObjectWithoutSubFields {
108          human
109        }",
110        TEST_SCHEMA,
111        &mut plan,
112    );
113
114    let messages = get_messages(&errors);
115    assert_eq!(messages.len(), 1);
116    assert_eq!(
117        messages,
118        vec!["Field \"human\" of type \"Human\" must have a selection of subfields. Did you mean \"human { ... }\"?"]
119    );
120}
121
122#[test]
123fn interface_type_missing_selection() {
124    use crate::validation::test_utils::*;
125
126    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
127    let errors = test_operation_with_schema(
128        "{
129          human { pets }
130        }",
131        TEST_SCHEMA,
132        &mut plan,
133    );
134
135    let messages = get_messages(&errors);
136    assert_eq!(messages.len(), 1);
137    assert_eq!(
138        messages,
139        vec!["Field \"pets\" of type \"[Pet]\" must have a selection of subfields. Did you mean \"pets { ... }\"?"]
140    );
141}
142
143#[test]
144fn selection_not_allowed_on_scalar() {
145    use crate::validation::test_utils::*;
146
147    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
148    let errors = test_operation_with_schema(
149        "fragment scalarSelectionsNotAllowedOnBoolean on Dog {
150          barks { sinceWhen }
151        }",
152        TEST_SCHEMA,
153        &mut plan,
154    );
155
156    let messages = get_messages(&errors);
157    assert_eq!(messages.len(), 1);
158    assert_eq!(
159        messages,
160        vec!["Field \"barks\" must not have a selection since type \"Boolean\" has no subfields."]
161    );
162}
163
164#[test]
165fn selection_not_allowed_on_enum() {
166    use crate::validation::test_utils::*;
167
168    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
169    let errors = test_operation_with_schema(
170        "fragment scalarSelectionsNotAllowedOnEnum on Cat {
171          furColor { inHexDec }
172        }",
173        TEST_SCHEMA,
174        &mut plan,
175    );
176
177    let messages = get_messages(&errors);
178    assert_eq!(messages.len(), 1);
179    assert_eq!(
180        messages,
181        vec!["Field \"furColor\" must not have a selection since type \"FurColor\" has no subfields."]
182    );
183}
184
185#[test]
186fn scalar_selection_not_allowed_with_args() {
187    use crate::validation::test_utils::*;
188
189    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
190    let errors = test_operation_with_schema(
191        "fragment scalarSelectionsNotAllowedWithArgs on Dog {
192          doesKnowCommand(dogCommand: SIT) { sinceWhen }
193        }",
194        TEST_SCHEMA,
195        &mut plan,
196    );
197
198    let messages = get_messages(&errors);
199    assert_eq!(messages.len(), 1);
200    assert_eq!(
201        messages,
202        vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."]
203    );
204}
205
206#[test]
207fn scalar_selection_not_allowed_with_directives() {
208    use crate::validation::test_utils::*;
209
210    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
211    let errors = test_operation_with_schema(
212        "fragment scalarSelectionsNotAllowedWithDirectives on Dog {
213          name @include(if: true) { isAlsoHumanName }
214        }",
215        TEST_SCHEMA,
216        &mut plan,
217    );
218
219    let messages = get_messages(&errors);
220    assert_eq!(messages.len(), 1);
221    assert_eq!(
222        messages,
223        vec!["Field \"name\" must not have a selection since type \"String\" has no subfields."]
224    );
225}
226
227#[test]
228fn scalar_selection_not_allowed_with_directives_and_args() {
229    use crate::validation::test_utils::*;
230
231    let mut plan = create_plan_from_rule(Box::new(LeafFieldSelections {}));
232    let errors = test_operation_with_schema(
233        "fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog {
234          doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen }
235        }",
236        TEST_SCHEMA,
237        &mut plan,
238    );
239
240    let messages = get_messages(&errors);
241    assert_eq!(messages.len(), 1);
242    assert_eq!(
243        messages,
244        vec!["Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields."]
245    );
246}