graphql_tools/validation/rules/
leaf_field_selections.rs1use super::ValidationRule;
2use crate::{
3 ast::{visit_document, OperationVisitor, OperationVisitorContext, TypeDefinitionExtension},
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<'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}