apollo_compiler/validation/
directive.rs1use super::CycleError;
2use crate::ast;
3use crate::collections::HashMap;
4use crate::collections::HashSet;
5use crate::coordinate::DirectiveArgumentCoordinate;
6use crate::coordinate::DirectiveCoordinate;
7use crate::schema;
8use crate::schema::validation::BuiltInScalars;
9use crate::validation::diagnostics::DiagnosticData;
10use crate::validation::DiagnosticList;
11use crate::validation::RecursionGuard;
12use crate::validation::RecursionStack;
13use crate::validation::SourceSpan;
14use crate::Node;
15
16struct FindRecursiveDirective<'s> {
19 schema: &'s schema::Schema,
20}
21
22impl FindRecursiveDirective<'_> {
23 fn type_definition(
24 &self,
25 directive_guard: &mut RecursionGuard<'_>,
26 type_guard: &mut RecursionGuard<'_>,
27 def: &schema::ExtendedType,
28 ) -> Result<(), CycleError<ast::Directive>> {
29 match def {
30 schema::ExtendedType::Scalar(scalar_type_definition) => {
31 self.directives(
32 directive_guard,
33 type_guard,
34 &scalar_type_definition.directives,
35 )?;
36 }
37 schema::ExtendedType::Object(object_type_definition) => {
38 self.directives(
39 directive_guard,
40 type_guard,
41 &object_type_definition.directives,
42 )?;
43 }
44 schema::ExtendedType::Interface(interface_type_definition) => {
45 self.directives(
46 directive_guard,
47 type_guard,
48 &interface_type_definition.directives,
49 )?;
50 }
51 schema::ExtendedType::Union(union_type_definition) => {
52 self.directives(
53 directive_guard,
54 type_guard,
55 &union_type_definition.directives,
56 )?;
57 }
58 schema::ExtendedType::Enum(enum_type_definition) => {
59 self.directives(
60 directive_guard,
61 type_guard,
62 &enum_type_definition.directives,
63 )?;
64 for enum_value in enum_type_definition.values.values() {
65 self.enum_value(directive_guard, type_guard, enum_value)?;
66 }
67 }
68 schema::ExtendedType::InputObject(input_type_definition) => {
69 self.directives(
70 directive_guard,
71 type_guard,
72 &input_type_definition.directives,
73 )?;
74 for input_value in input_type_definition.fields.values() {
75 self.input_value(directive_guard, type_guard, input_value)?;
76 }
77 }
78 }
79
80 Ok(())
81 }
82
83 fn input_value(
84 &self,
85 directive_guard: &mut RecursionGuard<'_>,
86 type_guard: &mut RecursionGuard<'_>,
87 input_value: &Node<ast::InputValueDefinition>,
88 ) -> Result<(), CycleError<ast::Directive>> {
89 for directive in &input_value.directives {
90 self.directive(directive_guard, type_guard, directive)?;
91 }
92
93 let type_name = input_value.ty.inner_named_type();
94 if let Some(type_def) = self.schema.types.get(type_name) {
95 if type_guard.contains(type_def.name()) {
96 return Ok(());
98 }
99 if !type_def.is_built_in() {
100 let mut new_type_guard = type_guard.push(type_def.name())?;
101 self.type_definition(directive_guard, &mut new_type_guard, type_def)?;
102 } else {
103 self.type_definition(directive_guard, type_guard, type_def)?;
104 }
105 }
106
107 Ok(())
108 }
109
110 fn enum_value(
111 &self,
112 directive_guard: &mut RecursionGuard<'_>,
113 type_guard: &mut RecursionGuard<'_>,
114 enum_value: &Node<ast::EnumValueDefinition>,
115 ) -> Result<(), CycleError<ast::Directive>> {
116 for directive in &enum_value.directives {
117 self.directive(directive_guard, type_guard, directive)?;
118 }
119
120 Ok(())
121 }
122
123 fn directives(
124 &self,
125 directive_guard: &mut RecursionGuard<'_>,
126 type_guard: &mut RecursionGuard<'_>,
127 directives: &[schema::Component<ast::Directive>],
128 ) -> Result<(), CycleError<ast::Directive>> {
129 for directive in directives {
130 self.directive(directive_guard, type_guard, directive)?;
131 }
132 Ok(())
133 }
134
135 fn directive(
136 &self,
137 directive_guard: &mut RecursionGuard<'_>,
138 type_guard: &mut RecursionGuard<'_>,
139 directive: &Node<ast::Directive>,
140 ) -> Result<(), CycleError<ast::Directive>> {
141 if !directive_guard.contains(&directive.name) {
142 if let Some(def) = self.schema.directive_definitions.get(&directive.name) {
143 let mut new_directive_guard = directive_guard.push(&directive.name)?;
144 self.directive_definition(&mut new_directive_guard, type_guard, def)
145 .map_err(|error| error.trace(directive))?;
146 }
147 } else if directive_guard.first() == Some(&directive.name) {
148 return Err(CycleError::Recursed(vec![directive.clone()]));
153 }
154
155 Ok(())
156 }
157
158 fn directive_definition(
159 &self,
160 directive_guard: &mut RecursionGuard<'_>,
161 type_guard: &mut RecursionGuard<'_>,
162 def: &Node<ast::DirectiveDefinition>,
163 ) -> Result<(), CycleError<ast::Directive>> {
164 for input_value in &def.arguments {
165 self.input_value(directive_guard, type_guard, input_value)?;
166 }
167
168 Ok(())
169 }
170
171 fn check(
172 schema: &schema::Schema,
173 directive_def: &Node<ast::DirectiveDefinition>,
174 ) -> Result<(), CycleError<ast::Directive>> {
175 let mut directive_stack = RecursionStack::with_root(directive_def.name.clone());
176 let mut directive_guard = directive_stack.guard();
177 let mut type_stack = RecursionStack::new();
178 let mut type_guard = type_stack.guard();
179 FindRecursiveDirective { schema }.directive_definition(
180 &mut directive_guard,
181 &mut type_guard,
182 directive_def,
183 )
184 }
185}
186
187pub(crate) fn validate_directive_definition(
188 diagnostics: &mut DiagnosticList,
189 schema: &crate::Schema,
190 built_in_scalars: &mut BuiltInScalars,
191 def: &Node<ast::DirectiveDefinition>,
192) {
193 schema::validation::validate_type_system_name(diagnostics, &def.name, "a directive definition");
194 super::input_object::validate_argument_definitions(
195 diagnostics,
196 schema,
197 built_in_scalars,
198 &def.arguments,
199 ast::DirectiveLocation::ArgumentDefinition,
200 );
201
202 let head_location = SourceSpan::recompose(def.location(), def.name.location());
203
204 match FindRecursiveDirective::check(schema, def) {
209 Ok(_) => {}
210 Err(CycleError::Recursed(trace)) => {
211 diagnostics.push(
212 head_location,
213 DiagnosticData::RecursiveDirectiveDefinition {
214 name: def.name.clone(),
215 trace,
216 },
217 );
218 }
219 Err(CycleError::Limit(_)) => diagnostics.push(
220 head_location,
221 DiagnosticData::DeeplyNestedType {
222 name: def.name.clone(),
223 describe_type: "directive",
224 },
225 ),
226 }
227}
228
229pub(crate) fn validate_directive_definitions(
230 diagnostics: &mut DiagnosticList,
231 schema: &crate::Schema,
232 built_in_scalars: &mut BuiltInScalars,
233) {
234 for directive_definition in schema.directive_definitions.values() {
235 validate_directive_definition(diagnostics, schema, built_in_scalars, directive_definition);
236 }
237}
238
239pub(crate) fn validate_directives<'dir>(
242 diagnostics: &mut DiagnosticList,
243 schema: Option<&crate::Schema>,
244 dirs: impl Iterator<Item = &'dir Node<ast::Directive>>,
245 dir_loc: ast::DirectiveLocation,
246 var_defs: &[Node<ast::VariableDefinition>],
247) {
248 let mut seen_directives = HashMap::<_, Option<SourceSpan>>::default();
249
250 for dir in dirs {
251 super::argument::validate_arguments(diagnostics, &dir.arguments);
252
253 let name = &dir.name;
254 let loc = dir.location();
255 let directive_definition =
256 schema.and_then(|schema| Some((schema, schema.directive_definitions.get(name)?)));
257
258 if let Some(&original_loc) = seen_directives.get(name) {
259 let is_repeatable = directive_definition
260 .map(|(_, def)| def.repeatable)
261 .unwrap_or(true);
263
264 if !is_repeatable {
265 diagnostics.push(
266 loc,
267 DiagnosticData::UniqueDirective {
268 name: name.clone(),
269 original_application: original_loc,
270 },
271 );
272 }
273 } else {
274 let loc = SourceSpan::recompose(dir.location(), dir.name.location());
275 seen_directives.insert(&dir.name, loc);
276 }
277
278 if let Some((schema, directive_definition)) = directive_definition {
279 let allowed_loc: HashSet<ast::DirectiveLocation> =
280 HashSet::from_iter(directive_definition.locations.iter().cloned());
281 if !allowed_loc.contains(&dir_loc) {
282 diagnostics.push(
283 loc,
284 DiagnosticData::UnsupportedLocation {
285 name: name.clone(),
286 location: dir_loc,
287 valid_locations: directive_definition.locations.clone(),
288 definition_location: directive_definition.location(),
289 },
290 );
291 }
292
293 for argument in &dir.arguments {
294 let input_value = directive_definition
295 .arguments
296 .iter()
297 .find(|val| val.name == argument.name);
298
299 if let Some(input_value) = input_value {
301 if super::variable::validate_variable_usage(
304 diagnostics,
305 input_value,
306 var_defs,
307 argument,
308 )
309 .is_ok()
310 {
311 super::value::validate_values(
312 diagnostics,
313 schema,
314 &input_value.ty,
315 argument,
316 var_defs,
317 );
318 }
319 } else {
320 diagnostics.push(
321 argument.location(),
322 DiagnosticData::UndefinedArgument {
323 name: argument.name.clone(),
324 coordinate: DirectiveCoordinate {
325 directive: dir.name.clone(),
326 }
327 .into(),
328 definition_location: loc,
329 },
330 );
331 }
332 }
333 for arg_def in &directive_definition.arguments {
334 let arg_value = dir
335 .arguments
336 .iter()
337 .find_map(|arg| (arg.name == arg_def.name).then_some(&arg.value));
338 let is_null = match arg_value {
339 None => true,
340 Some(value) => value.is_null(),
344 };
345
346 if arg_def.is_required() && is_null {
347 diagnostics.push(
348 dir.location(),
349 DiagnosticData::RequiredArgument {
350 name: arg_def.name.clone(),
351 expected_type: arg_def.ty.clone(),
352 coordinate: DirectiveArgumentCoordinate {
353 directive: directive_definition.name.clone(),
354 argument: arg_def.name.clone(),
355 }
356 .into(),
357 definition_location: arg_def.location(),
358 },
359 );
360 }
361 }
362 } else {
363 diagnostics.push(
364 loc,
365 DiagnosticData::UndefinedDirective { name: name.clone() },
366 )
367 }
368 }
369}