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,9 @@ private static boolean isNoNullArgWithoutDefaultValue(InputValueDefinition defin
182188 }
183189
184190 private void commonCheck (Collection <DirectiveDefinition > directiveDefinitions , List <GraphQLError > errors ) {
191+ Map <String , DirectiveDefinition > directiveDefinitionsByName = directiveDefinitionsByName (directiveDefinitions );
192+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName = directiveReferencesByName (directiveDefinitions );
193+
185194 directiveDefinitions .forEach (directiveDefinition -> {
186195 assertTypeName (directiveDefinition , errors );
187196 directiveDefinition .getInputValueDefinitions ().forEach (inputValueDefinition -> {
@@ -192,6 +201,123 @@ private void commonCheck(Collection<DirectiveDefinition> directiveDefinitions, L
192201 }
193202 });
194203 });
204+ checkIndirectDirectiveCycles (directiveDefinitionsByName , directiveReferencesByName , errors );
205+ }
206+
207+ private static Map <String , DirectiveDefinition > directiveDefinitionsByName (Collection <DirectiveDefinition > directiveDefinitions ) {
208+ Map <String , DirectiveDefinition > result = new LinkedHashMap <>();
209+ for (DirectiveDefinition directiveDefinition : directiveDefinitions ) {
210+ result .putIfAbsent (directiveDefinition .getName (), directiveDefinition );
211+ }
212+ return result ;
213+ }
214+
215+ private static Map <String , Map <String , InputValueDefinition >> directiveReferencesByName (
216+ Collection <DirectiveDefinition > directiveDefinitions ) {
217+ Map <String , Map <String , InputValueDefinition >> result = new LinkedHashMap <>();
218+ for (DirectiveDefinition directiveDefinition : directiveDefinitions ) {
219+ result .put (directiveDefinition .getName (), directiveReferences (directiveDefinition ));
220+ }
221+ return result ;
222+ }
223+
224+ private static Map <String , InputValueDefinition > directiveReferences (DirectiveDefinition directiveDefinition ) {
225+ Map <String , InputValueDefinition > result = new LinkedHashMap <>();
226+ for (InputValueDefinition inputValueDefinition : directiveDefinition .getInputValueDefinitions ()) {
227+ recordDirectiveReferences (directiveDefinition , result , inputValueDefinition );
228+ }
229+ return result ;
230+ }
231+
232+ private static void recordDirectiveReferences (DirectiveDefinition directiveDefinition ,
233+ Map <String , InputValueDefinition > result ,
234+ InputValueDefinition inputValueDefinition ) {
235+ for (Directive directive : inputValueDefinition .getDirectives ()) {
236+ if (directive .getName ().equals (directiveDefinition .getName ())) {
237+ continue ;
238+ }
239+ result .putIfAbsent (directive .getName (), inputValueDefinition );
240+ }
241+ }
242+
243+ private static void checkIndirectDirectiveCycles (
244+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
245+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
246+ List <GraphQLError > errors ) {
247+ Set <String > checked = new LinkedHashSet <>();
248+ Set <String > visiting = new LinkedHashSet <>();
249+ List <String > path = new ArrayList <>();
250+ for (String directiveName : directiveDefinitionsByName .keySet ()) {
251+ checkIndirectDirectiveCycles (directiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
252+ }
253+ }
254+
255+ private static void checkIndirectDirectiveCycles (String directiveName ,
256+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
257+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
258+ Set <String > checked ,
259+ Set <String > visiting ,
260+ List <String > path ,
261+ List <GraphQLError > errors ) {
262+ if (checked .contains (directiveName )) {
263+ return ;
264+ }
265+
266+ visiting .add (directiveName );
267+ path .add (directiveName );
268+ checkIndirectDirectiveCycleReferences (directiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
269+ path .remove (path .size () - 1 );
270+ visiting .remove (directiveName );
271+ checked .add (directiveName );
272+ }
273+
274+ private static void checkIndirectDirectiveCycleReferences (String directiveName ,
275+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
276+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
277+ Set <String > checked ,
278+ Set <String > visiting ,
279+ List <String > path ,
280+ List <GraphQLError > errors ) {
281+ Map <String , InputValueDefinition > references = directiveReferencesByName .getOrDefault (directiveName , Collections .emptyMap ());
282+ for (Map .Entry <String , InputValueDefinition > entry : references .entrySet ()) {
283+ checkIndirectDirectiveCycleReference (entry .getKey (), entry .getValue (), directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
284+ }
285+ }
286+
287+ private static void checkIndirectDirectiveCycleReference (String referencedDirectiveName ,
288+ InputValueDefinition inputValueDefinition ,
289+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
290+ Map <String , Map <String , InputValueDefinition >> directiveReferencesByName ,
291+ Set <String > checked ,
292+ Set <String > visiting ,
293+ List <String > path ,
294+ List <GraphQLError > errors ) {
295+ if (visiting .contains (referencedDirectiveName )) {
296+ addIndirectDirectiveCycleError (referencedDirectiveName , inputValueDefinition , directiveDefinitionsByName , path , errors );
297+ return ;
298+ }
299+ if (!checked .contains (referencedDirectiveName )) {
300+ checkIndirectDirectiveCycles (referencedDirectiveName , directiveDefinitionsByName , directiveReferencesByName , checked , visiting , path , errors );
301+ }
302+ }
303+
304+ private static void addIndirectDirectiveCycleError (String repeatedDirectiveName ,
305+ InputValueDefinition inputValueDefinition ,
306+ Map <String , DirectiveDefinition > directiveDefinitionsByName ,
307+ List <String > path ,
308+ List <GraphQLError > errors ) {
309+ List <String > cyclePath = directiveCyclePath (repeatedDirectiveName , path );
310+ String cyclePathString = String .join (" -> " , cyclePath );
311+
312+ DirectiveDefinition directiveDefinition = assertNotNull (directiveDefinitionsByName .get (repeatedDirectiveName ));
313+ errors .add (new DirectiveIllegalReferenceError (directiveDefinition , inputValueDefinition , cyclePathString ));
314+ }
315+
316+ private static List <String > directiveCyclePath (String repeatedDirectiveName , List <String > path ) {
317+ int cycleStart = path .indexOf (repeatedDirectiveName );
318+ List <String > cyclePath = new ArrayList <>(path .subList (cycleStart , path .size ()));
319+ cyclePath .add (repeatedDirectiveName );
320+ return cyclePath ;
195321 }
196322
197323 private static void assertTypeName (NamedNode <?> node , List <GraphQLError > errors ) {
@@ -224,4 +350,4 @@ private static TypeDefinition<?> findTypeDefFromRegistry(String typeName, TypeDe
224350 }
225351 return typeRegistry .scalars ().get (typeName );
226352 }
227- }
353+ }
0 commit comments