3030import graphql .schema .idl .errors .MissingTypeError ;
3131import graphql .schema .idl .errors .NotAnInputTypeError ;
3232
33+ import java .util .ArrayList ;
3334import java .util .Collection ;
35+ import java .util .Collections ;
36+ import java .util .LinkedHashMap ;
37+ import java .util .LinkedHashSet ;
3438import java .util .List ;
3539import java .util .Map ;
3640import java .util .Optional ;
41+ import java .util .Set ;
3742
43+ import static graphql .Assert .assertNotNull ;
3844import static graphql .introspection .Introspection .DirectiveLocation .ARGUMENT_DEFINITION ;
3945import static graphql .introspection .Introspection .DirectiveLocation .ENUM ;
4046import static graphql .introspection .Introspection .DirectiveLocation .ENUM_VALUE ;
@@ -182,6 +188,10 @@ private static boolean isNoNullArgWithoutDefaultValue(InputValueDefinition defin
182188 }
183189
184190 private void commonCheck (Collection <DirectiveDefinition > directiveDefinitions , List <GraphQLError > errors ) {
191+ List <DirectiveDefinition > directiveDefinitionsList = new ArrayList <>(directiveDefinitions );
192+ Map <String , DirectiveDefinition > directiveDefinitionsByName = getByName (directiveDefinitionsList , DirectiveDefinition ::getName , mergeFirst ());
193+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName = directiveReferencesByName (directiveDefinitionsByName );
194+
185195 directiveDefinitions .forEach (directiveDefinition -> {
186196 assertTypeName (directiveDefinition , errors );
187197 directiveDefinition .getInputValueDefinitions ().forEach (inputValueDefinition -> {
@@ -192,6 +202,113 @@ private void commonCheck(Collection<DirectiveDefinition> directiveDefinitions, L
192202 }
193203 });
194204 });
205+ checkIndirectDirectiveCycles (directiveDefinitionsByName , directiveReferencesByName , errors );
206+ }
207+
208+ private static Map <String , Map <String , InputValueDefinition >> directiveReferencesByName (
209+ Map <String , DirectiveDefinition > directiveDefinitionsByName ) {
210+ Map <String , Map <String , InputValueDefinition >> result = new LinkedHashMap <>();
211+ directiveDefinitionsByName .forEach ((name , directiveDefinition ) -> result .put (name , directiveReferences (directiveDefinition )));
212+ return result ;
213+ }
214+
215+ private static Map <String , InputValueDefinition > directiveReferences (DirectiveDefinition directiveDefinition ) {
216+ Map <String , InputValueDefinition > result = new LinkedHashMap <>();
217+ for (InputValueDefinition inputValueDefinition : directiveDefinition .getInputValueDefinitions ()) {
218+ recordDirectiveReferences (directiveDefinition , result , inputValueDefinition );
219+ }
220+ return result ;
221+ }
222+
223+ private static void recordDirectiveReferences (DirectiveDefinition directiveDefinition ,
224+ Map <String , InputValueDefinition > result ,
225+ InputValueDefinition inputValueDefinition ) {
226+ for (Directive directive : inputValueDefinition .getDirectives ()) {
227+ if (directive .getName ().equals (directiveDefinition .getName ())) {
228+ continue ;
229+ }
230+ result .putIfAbsent (directive .getName (), inputValueDefinition );
231+ }
232+ }
233+
234+ private static void checkIndirectDirectiveCycles (
235+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
236+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
237+ List <GraphQLError > errors ) {
238+ Set <String > checked = new LinkedHashSet <>();
239+ Set <String > visiting = new LinkedHashSet <>();
240+ List <String > path = new ArrayList <>();
241+ for (String directiveName : directiveDefinitionsByName .keySet ()) {
242+ checkIndirectDirectiveCycles (directiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
243+ }
244+ }
245+
246+ private static void checkIndirectDirectiveCycles (String directiveName ,
247+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
248+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
249+ Set <String > checked ,
250+ Set <String > visiting ,
251+ List <String > path ,
252+ List <GraphQLError > errors ) {
253+ if (checked .contains (directiveName )) {
254+ return ;
255+ }
256+
257+ visiting .add (directiveName );
258+ path .add (directiveName );
259+ checkIndirectDirectiveCycleReferences (directiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
260+ path .remove (path .size () - 1 );
261+ visiting .remove (directiveName );
262+ checked .add (directiveName );
263+ }
264+
265+ private static void checkIndirectDirectiveCycleReferences (String directiveName ,
266+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
267+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
268+ Set <String > checked ,
269+ Set <String > visiting ,
270+ List <String > path ,
271+ List <GraphQLError > errors ) {
272+ Map <String , InputValueDefinition > references = directiveReferencesByName .getOrDefault (directiveName , Collections .emptyMap ());
273+ for (Map .Entry <String , InputValueDefinition > entry : references .entrySet ()) {
274+ checkIndirectDirectiveCycleReference (entry .getKey (), entry .getValue (), directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
275+ }
276+ }
277+
278+ private static void checkIndirectDirectiveCycleReference (String referencedDirectiveName ,
279+ InputValueDefinition inputValueDefinition ,
280+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
281+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
282+ Set <String > checked ,
283+ Set <String > visiting ,
284+ List <String > path ,
285+ List <GraphQLError > errors ) {
286+ if (visiting .contains (referencedDirectiveName )) {
287+ addIndirectDirectiveCycleError (referencedDirectiveName , inputValueDefinition , directiveDefinitionsByName , path , errors );
288+ return ;
289+ }
290+ if (!checked .contains (referencedDirectiveName )) {
291+ checkIndirectDirectiveCycles (referencedDirectiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
292+ }
293+ }
294+
295+ private static void addIndirectDirectiveCycleError (String repeatedDirectiveName ,
296+ InputValueDefinition inputValueDefinition ,
297+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
298+ List <String > path ,
299+ List <GraphQLError > errors ) {
300+ List <String > cyclePath = directiveCyclePath (repeatedDirectiveName , path );
301+ String cyclePathString = String .join (" -> " , cyclePath );
302+
303+ DirectiveDefinition directiveDefinition = assertNotNull (directiveDefinitionsByName .get (repeatedDirectiveName ));
304+ errors .add (new DirectiveIllegalReferenceError (directiveDefinition , inputValueDefinition , cyclePathString ));
305+ }
306+
307+ private static List <String > directiveCyclePath (String repeatedDirectiveName , List <String > path ) {
308+ int cycleStart = path .indexOf (repeatedDirectiveName );
309+ List <String > cyclePath = new ArrayList <>(path .subList (cycleStart , path .size ()));
310+ cyclePath .add (repeatedDirectiveName );
311+ return cyclePath ;
195312 }
196313
197314 private static void assertTypeName (NamedNode <?> node , List <GraphQLError > errors ) {
@@ -224,4 +341,4 @@ private static TypeDefinition<?> findTypeDefFromRegistry(String typeName, TypeDe
224341 }
225342 return typeRegistry .scalars ().get (typeName );
226343 }
227- }
344+ }
0 commit comments