graphql_tools/validation/rules/
leaf_field_selections.rs1use super::ValidationRule;
2use crate::{
3 ast::{OperationVisitor, OperationVisitorContext},
4 validation::utils::{ValidationError, ValidationErrorContext},
5};
6
7pub 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}