Skip to content

Commit b1e42c3

Browse files
authored
fix(compiler): fix handling of directive arguments with nested types (#987)
1 parent 932c794 commit b1e42c3

File tree

2 files changed

+98
-25
lines changed

2 files changed

+98
-25
lines changed

crates/apollo-compiler/src/validation/directive.rs

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,57 @@ struct FindRecursiveDirective<'s> {
2222
impl FindRecursiveDirective<'_> {
2323
fn type_definition(
2424
&self,
25-
seen: &mut RecursionGuard<'_>,
25+
directive_guard: &mut RecursionGuard<'_>,
26+
type_guard: &mut RecursionGuard<'_>,
2627
def: &schema::ExtendedType,
2728
) -> Result<(), CycleError<ast::Directive>> {
2829
match def {
2930
schema::ExtendedType::Scalar(scalar_type_definition) => {
30-
self.directives(seen, &scalar_type_definition.directives)?;
31+
self.directives(
32+
directive_guard,
33+
type_guard,
34+
&scalar_type_definition.directives,
35+
)?;
3136
}
3237
schema::ExtendedType::Object(object_type_definition) => {
33-
self.directives(seen, &object_type_definition.directives)?;
38+
self.directives(
39+
directive_guard,
40+
type_guard,
41+
&object_type_definition.directives,
42+
)?;
3443
}
3544
schema::ExtendedType::Interface(interface_type_definition) => {
36-
self.directives(seen, &interface_type_definition.directives)?;
45+
self.directives(
46+
directive_guard,
47+
type_guard,
48+
&interface_type_definition.directives,
49+
)?;
3750
}
3851
schema::ExtendedType::Union(union_type_definition) => {
39-
self.directives(seen, &union_type_definition.directives)?;
52+
self.directives(
53+
directive_guard,
54+
type_guard,
55+
&union_type_definition.directives,
56+
)?;
4057
}
4158
schema::ExtendedType::Enum(enum_type_definition) => {
42-
self.directives(seen, &enum_type_definition.directives)?;
59+
self.directives(
60+
directive_guard,
61+
type_guard,
62+
&enum_type_definition.directives,
63+
)?;
4364
for enum_value in enum_type_definition.values.values() {
44-
self.enum_value(seen, enum_value)?;
65+
self.enum_value(directive_guard, type_guard, enum_value)?;
4566
}
4667
}
4768
schema::ExtendedType::InputObject(input_type_definition) => {
48-
self.directives(seen, &input_type_definition.directives)?;
69+
self.directives(
70+
directive_guard,
71+
type_guard,
72+
&input_type_definition.directives,
73+
)?;
4974
for input_value in input_type_definition.fields.values() {
50-
self.input_value(seen, input_value)?;
75+
self.input_value(directive_guard, type_guard, input_value)?;
5176
}
5277
}
5378
}
@@ -57,55 +82,69 @@ impl FindRecursiveDirective<'_> {
5782

5883
fn input_value(
5984
&self,
60-
seen: &mut RecursionGuard<'_>,
85+
directive_guard: &mut RecursionGuard<'_>,
86+
type_guard: &mut RecursionGuard<'_>,
6187
input_value: &Node<ast::InputValueDefinition>,
6288
) -> Result<(), CycleError<ast::Directive>> {
6389
for directive in &input_value.directives {
64-
self.directive(seen, directive)?;
90+
self.directive(directive_guard, type_guard, directive)?;
6591
}
6692

6793
let type_name = input_value.ty.inner_named_type();
6894
if let Some(type_def) = self.schema.types.get(type_name) {
69-
self.type_definition(seen, type_def)?;
95+
if type_guard.contains(type_def.name()) {
96+
// input type was already processed
97+
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+
}
70105
}
71106

72107
Ok(())
73108
}
74109

75110
fn enum_value(
76111
&self,
77-
seen: &mut RecursionGuard<'_>,
112+
directive_guard: &mut RecursionGuard<'_>,
113+
type_guard: &mut RecursionGuard<'_>,
78114
enum_value: &Node<ast::EnumValueDefinition>,
79115
) -> Result<(), CycleError<ast::Directive>> {
80116
for directive in &enum_value.directives {
81-
self.directive(seen, directive)?;
117+
self.directive(directive_guard, type_guard, directive)?;
82118
}
83119

84120
Ok(())
85121
}
86122

87123
fn directives(
88124
&self,
89-
seen: &mut RecursionGuard<'_>,
125+
directive_guard: &mut RecursionGuard<'_>,
126+
type_guard: &mut RecursionGuard<'_>,
90127
directives: &[schema::Component<ast::Directive>],
91128
) -> Result<(), CycleError<ast::Directive>> {
92129
for directive in directives {
93-
self.directive(seen, directive)?;
130+
self.directive(directive_guard, type_guard, directive)?;
94131
}
95132
Ok(())
96133
}
97134

98135
fn directive(
99136
&self,
100-
seen: &mut RecursionGuard<'_>,
137+
directive_guard: &mut RecursionGuard<'_>,
138+
type_guard: &mut RecursionGuard<'_>,
101139
directive: &Node<ast::Directive>,
102140
) -> Result<(), CycleError<ast::Directive>> {
103-
if !seen.contains(&directive.name) {
141+
if !directive_guard.contains(&directive.name) {
104142
if let Some(def) = self.schema.directive_definitions.get(&directive.name) {
105-
self.directive_definition(seen.push(&directive.name)?, def)
143+
let mut new_directive_guard = directive_guard.push(&directive.name)?;
144+
self.directive_definition(&mut new_directive_guard, type_guard, def)
106145
.map_err(|error| error.trace(directive))?;
107146
}
108-
} else if seen.first() == Some(&directive.name) {
147+
} else if directive_guard.first() == Some(&directive.name) {
109148
// Only report an error & bail out early if this is the *initial* directive.
110149
// This prevents raising confusing errors when a directive `@b` which is not
111150
// self-referential uses a directive `@a` that *is*. The error with `@a` should
@@ -118,11 +157,12 @@ impl FindRecursiveDirective<'_> {
118157

119158
fn directive_definition(
120159
&self,
121-
mut seen: RecursionGuard<'_>,
160+
directive_guard: &mut RecursionGuard<'_>,
161+
type_guard: &mut RecursionGuard<'_>,
122162
def: &Node<ast::DirectiveDefinition>,
123163
) -> Result<(), CycleError<ast::Directive>> {
124164
for input_value in &def.arguments {
125-
self.input_value(&mut seen, input_value)?;
165+
self.input_value(directive_guard, type_guard, input_value)?;
126166
}
127167

128168
Ok(())
@@ -132,9 +172,15 @@ impl FindRecursiveDirective<'_> {
132172
schema: &schema::Schema,
133173
directive_def: &Node<ast::DirectiveDefinition>,
134174
) -> Result<(), CycleError<ast::Directive>> {
135-
let mut recursion_stack = RecursionStack::with_root(directive_def.name.clone());
136-
FindRecursiveDirective { schema }
137-
.directive_definition(recursion_stack.guard(), directive_def)
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+
)
138184
}
139185
}
140186

crates/apollo-compiler/tests/validation/recursion.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use apollo_compiler::parser::Parser;
2+
use apollo_compiler::ExecutableDocument;
3+
use apollo_compiler::Schema;
24
use expect_test::expect;
35

46
/// Build a chain of `size` fragments where each fragment recurses into a field and applies the
@@ -347,3 +349,28 @@ type Query {
347349
"#]]
348350
.assert_eq(&errors.to_string());
349351
}
352+
353+
#[test]
354+
fn handles_directive_with_nested_input_types() {
355+
let schema = r#"
356+
directive @custom(input: NestedInput) on OBJECT | INTERFACE
357+
358+
input NestedInput {
359+
name: String
360+
nested: NestedInput
361+
}
362+
363+
type Query {
364+
foo: String
365+
}
366+
"#;
367+
368+
let input_executable = r#"
369+
query {
370+
foo
371+
}
372+
"#;
373+
374+
let schema = Schema::parse_and_validate(schema, "schema.graphql").unwrap();
375+
ExecutableDocument::parse_and_validate(&schema, input_executable, "query.graphql").unwrap();
376+
}

0 commit comments

Comments
 (0)