From 0b5486dd58b91858e85634e8a2dd034129d3ad8a Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Tue, 19 May 2026 09:16:47 +1000 Subject: [PATCH 1/2] Reject indirect directive definition cycles --- .../idl/SchemaTypeDirectivesChecker.java | 119 +++++++++++++++++- .../DirectiveIllegalReferenceError.java | 9 +- .../schema/idl/SchemaGeneratorTest.groovy | 41 ++++++ .../SchemaTypeDirectivesCheckerTest.groovy | 45 +++++++ 4 files changed, 212 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java b/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java index 4c3e373e37..b5d4ce13da 100644 --- a/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java +++ b/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java @@ -30,11 +30,17 @@ import graphql.schema.idl.errors.MissingTypeError; import graphql.schema.idl.errors.NotAnInputTypeError; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import static graphql.Assert.assertNotNull; import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION; import static graphql.introspection.Introspection.DirectiveLocation.ENUM; import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE; @@ -182,6 +188,10 @@ private static boolean isNoNullArgWithoutDefaultValue(InputValueDefinition defin } private void commonCheck(Collection directiveDefinitions, List errors) { + List directiveDefinitionsList = new ArrayList<>(directiveDefinitions); + Map directiveDefinitionsByName = getByName(directiveDefinitionsList, DirectiveDefinition::getName, mergeFirst()); + Map> directiveReferencesByName = directiveReferencesByName(directiveDefinitionsByName); + directiveDefinitions.forEach(directiveDefinition -> { assertTypeName(directiveDefinition, errors); directiveDefinition.getInputValueDefinitions().forEach(inputValueDefinition -> { @@ -192,6 +202,113 @@ private void commonCheck(Collection directiveDefinitions, L } }); }); + checkIndirectDirectiveCycles(directiveDefinitionsByName, directiveReferencesByName, errors); + } + + private static Map> directiveReferencesByName( + Map directiveDefinitionsByName) { + Map> result = new LinkedHashMap<>(); + directiveDefinitionsByName.forEach((name, directiveDefinition) -> result.put(name, directiveReferences(directiveDefinition))); + return result; + } + + private static Map directiveReferences(DirectiveDefinition directiveDefinition) { + Map result = new LinkedHashMap<>(); + for (InputValueDefinition inputValueDefinition : directiveDefinition.getInputValueDefinitions()) { + recordDirectiveReferences(directiveDefinition, result, inputValueDefinition); + } + return result; + } + + private static void recordDirectiveReferences(DirectiveDefinition directiveDefinition, + Map result, + InputValueDefinition inputValueDefinition) { + for (Directive directive : inputValueDefinition.getDirectives()) { + if (directive.getName().equals(directiveDefinition.getName())) { + continue; + } + result.putIfAbsent(directive.getName(), inputValueDefinition); + } + } + + private static void checkIndirectDirectiveCycles( + Map directiveDefinitionsByName, + Map> directiveReferencesByName, + List errors) { + Set checked = new LinkedHashSet<>(); + Set visiting = new LinkedHashSet<>(); + List path = new ArrayList<>(); + for (String directiveName : directiveDefinitionsByName.keySet()) { + checkIndirectDirectiveCycles(directiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + } + } + + private static void checkIndirectDirectiveCycles(String directiveName, + Map directiveDefinitionsByName, + Map> directiveReferencesByName, + Set checked, + Set visiting, + List path, + List errors) { + if (checked.contains(directiveName)) { + return; + } + + visiting.add(directiveName); + path.add(directiveName); + checkIndirectDirectiveCycleReferences(directiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + path.remove(path.size() - 1); + visiting.remove(directiveName); + checked.add(directiveName); + } + + private static void checkIndirectDirectiveCycleReferences(String directiveName, + Map directiveDefinitionsByName, + Map> directiveReferencesByName, + Set checked, + Set visiting, + List path, + List errors) { + Map references = directiveReferencesByName.getOrDefault(directiveName, Collections.emptyMap()); + for (Map.Entry entry : references.entrySet()) { + checkIndirectDirectiveCycleReference(entry.getKey(), entry.getValue(), directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + } + } + + private static void checkIndirectDirectiveCycleReference(String referencedDirectiveName, + InputValueDefinition inputValueDefinition, + Map directiveDefinitionsByName, + Map> directiveReferencesByName, + Set checked, + Set visiting, + List path, + List errors) { + if (visiting.contains(referencedDirectiveName)) { + addIndirectDirectiveCycleError(referencedDirectiveName, inputValueDefinition, directiveDefinitionsByName, path, errors); + return; + } + if (!checked.contains(referencedDirectiveName)) { + checkIndirectDirectiveCycles(referencedDirectiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + } + } + + private static void addIndirectDirectiveCycleError(String repeatedDirectiveName, + InputValueDefinition inputValueDefinition, + Map directiveDefinitionsByName, + List path, + List errors) { + List cyclePath = directiveCyclePath(repeatedDirectiveName, path); + String cyclePathString = String.join(" -> ", cyclePath); + + DirectiveDefinition directiveDefinition = assertNotNull(directiveDefinitionsByName.get(repeatedDirectiveName)); + errors.add(new DirectiveIllegalReferenceError(directiveDefinition, inputValueDefinition, cyclePathString)); + } + + private static List directiveCyclePath(String repeatedDirectiveName, List path) { + int cycleStart = path.indexOf(repeatedDirectiveName); + List cyclePath = new ArrayList<>(path.subList(cycleStart, path.size())); + cyclePath.add(repeatedDirectiveName); + return cyclePath; } private static void assertTypeName(NamedNode node, List errors) { @@ -224,4 +341,4 @@ private static TypeDefinition findTypeDefFromRegistry(String typeName, TypeDe } return typeRegistry.scalars().get(typeName); } -} \ No newline at end of file +} diff --git a/src/main/java/graphql/schema/idl/errors/DirectiveIllegalReferenceError.java b/src/main/java/graphql/schema/idl/errors/DirectiveIllegalReferenceError.java index 44fb541e53..2bcd34db39 100644 --- a/src/main/java/graphql/schema/idl/errors/DirectiveIllegalReferenceError.java +++ b/src/main/java/graphql/schema/idl/errors/DirectiveIllegalReferenceError.java @@ -12,4 +12,11 @@ public DirectiveIllegalReferenceError(DirectiveDefinition directive, NamedNode l directive.getName(), location.getName(), lineCol(location) )); } -} \ No newline at end of file + + public DirectiveIllegalReferenceError(DirectiveDefinition directive, NamedNode location, String cyclePath) { + super(directive, + String.format("'%s' must not reference itself via directive cycle '%s' on '%s''%s'", + directive.getName(), cyclePath, location.getName(), lineCol(location) + )); + } +} diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 5c268ebd0f..b6d084e5c4 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -25,6 +25,7 @@ import graphql.schema.GraphQLType import graphql.schema.GraphQLTypeUtil import graphql.schema.GraphQLUnionType import graphql.schema.GraphqlTypeComparatorRegistry +import graphql.schema.idl.errors.DirectiveIllegalReferenceError import graphql.schema.idl.errors.NotAnInputTypeError import graphql.schema.idl.errors.NotAnOutputTypeError import graphql.schema.idl.errors.SchemaProblem @@ -2270,6 +2271,46 @@ class SchemaGeneratorTest extends Specification { schema != null } + def "#4201 indirect cyclical directive definitions are rejected without stack overflow - #name"() { + given: + def registry = new SchemaParser().parse(sdl) + + when: + UnExecutableSchemaGenerator.makeUnExecutableSchema(registry) + + then: + def e = thrown(SchemaProblem) + e.errors.size() == 1 + e.errors.get(0) instanceof DirectiveIllegalReferenceError + e.errors.get(0).getMessage().contains(cycleMessage) + + where: + name << ["two directives", "three directives"] + sdl << [ + ''' + directive @foo(x: Int @bar(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + directive @bar(y: Int @foo(x: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + + type Query { + field: String @foo(x: 10) @bar(y: 20) + } + ''', + ''' + directive @dirA(x: Int @dirB(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + directive @dirB(y: Int @dirC(z: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + directive @dirC(z: Int @dirA(x: 3)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + + type Query { + field: String @dirA(x: 10) @dirB(y: 20) @dirC(z: 30) + } + ''' + ] + cycleMessage << [ + "'foo' must not reference itself via directive cycle 'foo -> bar -> foo'", + "'dirA' must not reference itself via directive cycle 'dirA -> dirB -> dirC -> dirA'" + ] + } + def "code registry default data fetcher is respected"() { def sdl = ''' type Query { diff --git a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy index 887fe97f5c..4debd015b9 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy @@ -232,6 +232,51 @@ class SchemaTypeDirectivesCheckerTest extends Specification { errors.get(0).getMessage() == "'invalidExample' must not reference itself on 'arg''[@2:39]'" } + def "directive must not indirectly reference itself"() { + given: + def spec = ''' + directive @foo(arg: String @bar) on ARGUMENT_DEFINITION + directive @bar(arg: String @foo) on ARGUMENT_DEFINITION + + type Query { + f1 : String + } + ''' + def registry = parse(spec) + def errors = [] + + when: + new SchemaTypeDirectivesChecker(registry, RuntimeWiring.newRuntimeWiring().build()).checkTypeDirectives(errors) + + then: + errors.size() == 1 + errors.get(0) instanceof DirectiveIllegalReferenceError + errors.get(0).getMessage().contains("'foo' must not reference itself via directive cycle 'foo -> bar -> foo'") + } + + def "directive must not indirectly reference itself through a longer cycle"() { + given: + def spec = ''' + directive @dirA(x: Int @dirB(y: 1)) on ARGUMENT_DEFINITION + directive @dirB(y: Int @dirC(z: 2)) on ARGUMENT_DEFINITION + directive @dirC(z: Int @dirA(x: 3)) on ARGUMENT_DEFINITION + + type Query { + f1 : String + } + ''' + def registry = parse(spec) + def errors = [] + + when: + new SchemaTypeDirectivesChecker(registry, RuntimeWiring.newRuntimeWiring().build()).checkTypeDirectives(errors) + + then: + errors.size() == 1 + errors.get(0) instanceof DirectiveIllegalReferenceError + errors.get(0).getMessage().contains("'dirA' must not reference itself via directive cycle 'dirA -> dirB -> dirC -> dirA'") + } + def "directive must not begin with '__'"() { given: def spec = ''' From 91589010604c7c38ead78c3b536277d2cde53102 Mon Sep 17 00:00:00 2001 From: Andreas Marek Date: Mon, 24 Aug 2026 08:17:31 +1000 Subject: [PATCH 2/2] Simplify directive cycle detection --- .../idl/SchemaTypeDirectivesChecker.java | 104 ++++++------------ .../schema/idl/SchemaGeneratorTest.groovy | 39 ++----- .../SchemaTypeDirectivesCheckerTest.groovy | 55 +++++---- 3 files changed, 76 insertions(+), 122 deletions(-) diff --git a/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java b/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java index b5d4ce13da..18011537ff 100644 --- a/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java +++ b/src/main/java/graphql/schema/idl/SchemaTypeDirectivesChecker.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -190,7 +189,6 @@ private static boolean isNoNullArgWithoutDefaultValue(InputValueDefinition defin private void commonCheck(Collection directiveDefinitions, List errors) { List directiveDefinitionsList = new ArrayList<>(directiveDefinitions); Map directiveDefinitionsByName = getByName(directiveDefinitionsList, DirectiveDefinition::getName, mergeFirst()); - Map> directiveReferencesByName = directiveReferencesByName(directiveDefinitionsByName); directiveDefinitions.forEach(directiveDefinition -> { assertTypeName(directiveDefinition, errors); @@ -202,14 +200,7 @@ private void commonCheck(Collection directiveDefinitions, L } }); }); - checkIndirectDirectiveCycles(directiveDefinitionsByName, directiveReferencesByName, errors); - } - - private static Map> directiveReferencesByName( - Map directiveDefinitionsByName) { - Map> result = new LinkedHashMap<>(); - directiveDefinitionsByName.forEach((name, directiveDefinition) -> result.put(name, directiveReferences(directiveDefinition))); - return result; + checkIndirectDirectiveCycles(directiveDefinitionsByName, errors); } private static Map directiveReferences(DirectiveDefinition directiveDefinition) { @@ -233,82 +224,49 @@ private static void recordDirectiveReferences(DirectiveDefinition directiveDefin private static void checkIndirectDirectiveCycles( Map directiveDefinitionsByName, - Map> directiveReferencesByName, List errors) { Set checked = new LinkedHashSet<>(); - Set visiting = new LinkedHashSet<>(); - List path = new ArrayList<>(); - for (String directiveName : directiveDefinitionsByName.keySet()) { - checkIndirectDirectiveCycles(directiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + // Insertion order records the active recursion path for the error message. + LinkedHashSet currentPath = new LinkedHashSet<>(); + for (DirectiveDefinition directiveDefinition : directiveDefinitionsByName.values()) { + checkDirectiveReferencesForCycles(directiveDefinition, directiveDefinitionsByName, checked, currentPath, errors); } } - private static void checkIndirectDirectiveCycles(String directiveName, - Map directiveDefinitionsByName, - Map> directiveReferencesByName, - Set checked, - Set visiting, - List path, - List errors) { + private static void checkDirectiveReferencesForCycles(DirectiveDefinition directiveDefinition, + Map directiveDefinitionsByName, + Set checked, + LinkedHashSet currentPath, + List errors) { + String directiveName = directiveDefinition.getName(); if (checked.contains(directiveName)) { return; } - visiting.add(directiveName); - path.add(directiveName); - checkIndirectDirectiveCycleReferences(directiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); - path.remove(path.size() - 1); - visiting.remove(directiveName); - checked.add(directiveName); - } - - private static void checkIndirectDirectiveCycleReferences(String directiveName, - Map directiveDefinitionsByName, - Map> directiveReferencesByName, - Set checked, - Set visiting, - List path, - List errors) { - Map references = directiveReferencesByName.getOrDefault(directiveName, Collections.emptyMap()); - for (Map.Entry entry : references.entrySet()) { - checkIndirectDirectiveCycleReference(entry.getKey(), entry.getValue(), directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); - } - } - - private static void checkIndirectDirectiveCycleReference(String referencedDirectiveName, - InputValueDefinition inputValueDefinition, - Map directiveDefinitionsByName, - Map> directiveReferencesByName, - Set checked, - Set visiting, - List path, - List errors) { - if (visiting.contains(referencedDirectiveName)) { - addIndirectDirectiveCycleError(referencedDirectiveName, inputValueDefinition, directiveDefinitionsByName, path, errors); - return; - } - if (!checked.contains(referencedDirectiveName)) { - checkIndirectDirectiveCycles(referencedDirectiveName, directiveDefinitionsByName, directiveReferencesByName, checked, visiting, path, errors); + currentPath.add(directiveName); + for (Map.Entry reference : directiveReferences(directiveDefinition).entrySet()) { + String referencedDirectiveName = reference.getKey(); + if (currentPath.contains(referencedDirectiveName)) { + DirectiveDefinition repeatedDirective = assertNotNull(directiveDefinitionsByName.get(referencedDirectiveName)); + String cyclePath = directiveCyclePath(referencedDirectiveName, currentPath); + errors.add(new DirectiveIllegalReferenceError(repeatedDirective, reference.getValue(), cyclePath)); + continue; + } + DirectiveDefinition referencedDirective = directiveDefinitionsByName.get(referencedDirectiveName); + if (referencedDirective != null) { + checkDirectiveReferencesForCycles(referencedDirective, directiveDefinitionsByName, checked, currentPath, errors); + } } + currentPath.remove(directiveName); + checked.add(directiveName); } - private static void addIndirectDirectiveCycleError(String repeatedDirectiveName, - InputValueDefinition inputValueDefinition, - Map directiveDefinitionsByName, - List path, - List errors) { - List cyclePath = directiveCyclePath(repeatedDirectiveName, path); - String cyclePathString = String.join(" -> ", cyclePath); - - DirectiveDefinition directiveDefinition = assertNotNull(directiveDefinitionsByName.get(repeatedDirectiveName)); - errors.add(new DirectiveIllegalReferenceError(directiveDefinition, inputValueDefinition, cyclePathString)); - } - - private static List directiveCyclePath(String repeatedDirectiveName, List path) { - int cycleStart = path.indexOf(repeatedDirectiveName); - List cyclePath = new ArrayList<>(path.subList(cycleStart, path.size())); + private static String directiveCyclePath(String repeatedDirectiveName, LinkedHashSet currentPath) { + List pathList = new ArrayList<>(currentPath); + int cycleStart = pathList.indexOf(repeatedDirectiveName); + List cyclePath = new ArrayList<>(pathList.subList(cycleStart, pathList.size())); cyclePath.add(repeatedDirectiveName); - return cyclePath; + return String.join(" -> ", cyclePath); } private static void assertTypeName(NamedNode node, List errors) { diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index b6d084e5c4..3f375eeb8e 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -2271,9 +2271,16 @@ class SchemaGeneratorTest extends Specification { schema != null } - def "#4201 indirect cyclical directive definitions are rejected without stack overflow - #name"() { + def "#4201 indirect cyclical directive definitions are rejected without stack overflow"() { given: - def registry = new SchemaParser().parse(sdl) + def registry = new SchemaParser().parse(''' + directive @foo(x: Int @bar(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + directive @bar(y: Int @foo(x: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION + + type Query { + field: String @foo(x: 10) @bar(y: 20) + } + ''') when: UnExecutableSchemaGenerator.makeUnExecutableSchema(registry) @@ -2282,33 +2289,7 @@ class SchemaGeneratorTest extends Specification { def e = thrown(SchemaProblem) e.errors.size() == 1 e.errors.get(0) instanceof DirectiveIllegalReferenceError - e.errors.get(0).getMessage().contains(cycleMessage) - - where: - name << ["two directives", "three directives"] - sdl << [ - ''' - directive @foo(x: Int @bar(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION - directive @bar(y: Int @foo(x: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION - - type Query { - field: String @foo(x: 10) @bar(y: 20) - } - ''', - ''' - directive @dirA(x: Int @dirB(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION - directive @dirB(y: Int @dirC(z: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION - directive @dirC(z: Int @dirA(x: 3)) on FIELD_DEFINITION | ARGUMENT_DEFINITION - - type Query { - field: String @dirA(x: 10) @dirB(y: 20) @dirC(z: 30) - } - ''' - ] - cycleMessage << [ - "'foo' must not reference itself via directive cycle 'foo -> bar -> foo'", - "'dirA' must not reference itself via directive cycle 'dirA -> dirB -> dirC -> dirA'" - ] + e.errors.get(0).getMessage().contains("'foo' must not reference itself via directive cycle 'foo -> bar -> foo'") } def "code registry default data fetcher is respected"() { diff --git a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy index 4debd015b9..f8096c2b28 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaTypeDirectivesCheckerTest.groovy @@ -232,16 +232,8 @@ class SchemaTypeDirectivesCheckerTest extends Specification { errors.get(0).getMessage() == "'invalidExample' must not reference itself on 'arg''[@2:39]'" } - def "directive must not indirectly reference itself"() { + def "directive must not indirectly reference itself - #name"() { given: - def spec = ''' - directive @foo(arg: String @bar) on ARGUMENT_DEFINITION - directive @bar(arg: String @foo) on ARGUMENT_DEFINITION - - type Query { - f1 : String - } - ''' def registry = parse(spec) def errors = [] @@ -251,30 +243,53 @@ class SchemaTypeDirectivesCheckerTest extends Specification { then: errors.size() == 1 errors.get(0) instanceof DirectiveIllegalReferenceError - errors.get(0).getMessage().contains("'foo' must not reference itself via directive cycle 'foo -> bar -> foo'") + errors.get(0).getMessage().contains(cycleMessage) + + where: + name << ["two directives", "three directives"] + spec << [ + ''' + directive @foo(arg: String @bar) on ARGUMENT_DEFINITION + directive @bar(arg: String @foo) on ARGUMENT_DEFINITION + + type Query { + f1 : String + } + ''', + ''' + directive @dirA(x: Int @dirB(y: 1)) on ARGUMENT_DEFINITION + directive @dirB(y: Int @dirC(z: 2)) on ARGUMENT_DEFINITION + directive @dirC(z: Int @dirA(x: 3)) on ARGUMENT_DEFINITION + + type Query { + f1 : String + } + ''' + ] + cycleMessage << [ + "'foo' must not reference itself via directive cycle 'foo -> bar -> foo'", + "'dirA' must not reference itself via directive cycle 'dirA -> dirB -> dirC -> dirA'" + ] } - def "directive must not indirectly reference itself through a longer cycle"() { + def "acyclic directive references are allowed"() { given: - def spec = ''' - directive @dirA(x: Int @dirB(y: 1)) on ARGUMENT_DEFINITION - directive @dirB(y: Int @dirC(z: 2)) on ARGUMENT_DEFINITION - directive @dirC(z: Int @dirA(x: 3)) on ARGUMENT_DEFINITION + def registry = parse(''' + directive @foo(arg: String @bar) on ARGUMENT_DEFINITION + directive @bar(arg: String @baz) on ARGUMENT_DEFINITION + directive @baz on ARGUMENT_DEFINITION type Query { f1 : String } - ''' - def registry = parse(spec) + ''') def errors = [] when: new SchemaTypeDirectivesChecker(registry, RuntimeWiring.newRuntimeWiring().build()).checkTypeDirectives(errors) then: - errors.size() == 1 - errors.get(0) instanceof DirectiveIllegalReferenceError - errors.get(0).getMessage().contains("'dirA' must not reference itself via directive cycle 'dirA -> dirB -> dirC -> dirA'") + errors.isEmpty() } def "directive must not begin with '__'"() {