77import graphql .language .Directive ;
88import graphql .language .DirectiveDefinition ;
99import graphql .language .EnumTypeDefinition ;
10+ import graphql .language .EnumTypeExtensionDefinition ;
1011import graphql .language .EnumValueDefinition ;
1112import graphql .language .FieldDefinition ;
1213import graphql .language .InputObjectTypeDefinition ;
14+ import graphql .language .InputObjectTypeExtensionDefinition ;
1315import graphql .language .InputValueDefinition ;
1416import graphql .language .InterfaceTypeDefinition ;
1517import graphql .language .NamedNode ;
1618import graphql .language .Node ;
1719import graphql .language .NonNullType ;
1820import graphql .language .ObjectTypeDefinition ;
1921import graphql .language .ScalarTypeDefinition ;
22+ import graphql .language .ScalarTypeExtensionDefinition ;
2023import graphql .language .SchemaDefinition ;
2124import graphql .language .TypeDefinition ;
2225import graphql .language .TypeName ;
3033import graphql .schema .idl .errors .MissingTypeError ;
3134import graphql .schema .idl .errors .NotAnInputTypeError ;
3235
36+ import java .util .ArrayList ;
3337import java .util .Collection ;
38+ import java .util .Collections ;
39+ import java .util .LinkedHashSet ;
3440import java .util .List ;
3541import java .util .Map ;
3642import java .util .Optional ;
43+ import java .util .Set ;
3744
3845import static graphql .introspection .Introspection .DirectiveLocation .ARGUMENT_DEFINITION ;
3946import static graphql .introspection .Introspection .DirectiveLocation .ENUM ;
@@ -184,16 +191,205 @@ private static boolean isNoNullArgWithoutDefaultValue(InputValueDefinition defin
184191 private void commonCheck (Collection <DirectiveDefinition > directiveDefinitions , List <GraphQLError > errors ) {
185192 directiveDefinitions .forEach (directiveDefinition -> {
186193 assertTypeName (directiveDefinition , errors );
187- directiveDefinition .getInputValueDefinitions ().forEach (inputValueDefinition -> {
194+ boolean hasDirectSelfReference = false ;
195+ for (InputValueDefinition inputValueDefinition : directiveDefinition .getInputValueDefinitions ()) {
188196 assertTypeName (inputValueDefinition , errors );
189197 assertExistAndIsInputType (inputValueDefinition , errors );
190198 if (inputValueDefinition .hasDirective (directiveDefinition .getName ())) {
191199 errors .add (new DirectiveIllegalReferenceError (directiveDefinition , inputValueDefinition ));
200+ hasDirectSelfReference = true ;
192201 }
193- });
202+ }
203+ if (hasDirectSelfReference ) {
204+ return ;
205+ }
206+
207+ String cycle = findDirectiveCycle (directiveDefinition );
208+ if (cycle != null ) {
209+ errors .add (new DirectiveIllegalReferenceError (directiveDefinition , cycle ));
210+ }
194211 });
195212 }
196213
214+ private String findDirectiveCycle (DirectiveDefinition directiveDefinition ) {
215+ List <String > path = new ArrayList <>();
216+ path .add (directiveName (directiveDefinition .getName ()));
217+
218+ Set <String > visited = new LinkedHashSet <>();
219+ visited .add (directiveName (directiveDefinition .getName ()));
220+
221+ return findCycleFromInputValueDefinitions (directiveDefinition .getInputValueDefinitions (), directiveDefinition .getName (), path , visited );
222+ }
223+
224+ private String findCycleFromInputValueDefinitions (List <InputValueDefinition > inputValueDefinitions , String startDirectiveName , List <String > path , Set <String > visited ) {
225+ for (InputValueDefinition inputValueDefinition : inputValueDefinitions ) {
226+ String cycle = findCycleFromDirectives (inputValueDefinition .getDirectives (), startDirectiveName , path , visited );
227+ if (cycle != null ) {
228+ return cycle ;
229+ }
230+
231+ cycle = findCycleFromInputType (inputValueDefinition .getType (), startDirectiveName , path , visited );
232+ if (cycle != null ) {
233+ return cycle ;
234+ }
235+ }
236+ return null ;
237+ }
238+
239+ private String findCycleFromDirectives (List <Directive > directives , String startDirectiveName , List <String > path , Set <String > visited ) {
240+ for (Directive directive : directives ) {
241+ String cycle = findCycleFromDirectiveReference (directive .getName (), startDirectiveName , path , visited );
242+ if (cycle != null ) {
243+ return cycle ;
244+ }
245+ }
246+ return null ;
247+ }
248+
249+ private String findCycleFromDirectiveReference (String directiveName , String startDirectiveName , List <String > path , Set <String > visited ) {
250+ String displayName = directiveName (directiveName );
251+ if (directiveName .equals (startDirectiveName )) {
252+ return cyclePath (path , displayName );
253+ }
254+ if (visited .contains (displayName )) {
255+ return null ;
256+ }
257+
258+ Optional <DirectiveDefinition > directiveDefinition = typeRegistry .getDirectiveDefinition (directiveName );
259+ if (directiveDefinition .isEmpty ()) {
260+ return null ;
261+ }
262+
263+ return findCycleFromInputValueDefinitions (
264+ directiveDefinition .get ().getInputValueDefinitions (),
265+ startDirectiveName ,
266+ addToPath (path , displayName ),
267+ addToVisited (visited , displayName )
268+ );
269+ }
270+
271+ private String findCycleFromInputType (graphql .language .Type <?> type , String startDirectiveName , List <String > path , Set <String > visited ) {
272+ TypeDefinition <?> typeDefinition = findTypeDefFromRegistry (TypeUtil .unwrapAll (type ).getName (), typeRegistry );
273+ if (typeDefinition == null ) {
274+ return null ;
275+ }
276+ if (visited .contains (typeDefinition .getName ())) {
277+ return null ;
278+ }
279+
280+ List <String > nextPath = addToPath (path , typeDefinition .getName ());
281+ Set <String > nextVisited = addToVisited (visited , typeDefinition .getName ());
282+
283+ if (typeDefinition instanceof ScalarTypeDefinition ) {
284+ return findCycleFromScalarType ((ScalarTypeDefinition ) typeDefinition , startDirectiveName , nextPath , nextVisited );
285+ }
286+ if (typeDefinition instanceof EnumTypeDefinition ) {
287+ return findCycleFromEnumType ((EnumTypeDefinition ) typeDefinition , startDirectiveName , nextPath , nextVisited );
288+ }
289+ if (typeDefinition instanceof InputObjectTypeDefinition ) {
290+ return findCycleFromInputObjectType ((InputObjectTypeDefinition ) typeDefinition , startDirectiveName , nextPath , nextVisited );
291+ }
292+ return null ;
293+ }
294+
295+ private String findCycleFromScalarType (ScalarTypeDefinition typeDefinition , String startDirectiveName , List <String > path , Set <String > visited ) {
296+ String cycle = findCycleFromDirectives (typeDefinition .getDirectives (), startDirectiveName , path , visited );
297+ if (cycle != null ) {
298+ return cycle ;
299+ }
300+
301+ List <ScalarTypeExtensionDefinition > extensions = typeRegistry .scalarTypeExtensions ().getOrDefault (typeDefinition .getName (), Collections .emptyList ());
302+ for (ScalarTypeExtensionDefinition extension : extensions ) {
303+ cycle = findCycleFromDirectives (extension .getDirectives (), startDirectiveName , path , visited );
304+ if (cycle != null ) {
305+ return cycle ;
306+ }
307+ }
308+ return null ;
309+ }
310+
311+ private String findCycleFromEnumType (EnumTypeDefinition typeDefinition , String startDirectiveName , List <String > path , Set <String > visited ) {
312+ String cycle = findCycleFromDirectives (typeDefinition .getDirectives (), startDirectiveName , path , visited );
313+ if (cycle != null ) {
314+ return cycle ;
315+ }
316+
317+ cycle = findCycleFromEnumValues (typeDefinition .getEnumValueDefinitions (), startDirectiveName , path , visited );
318+ if (cycle != null ) {
319+ return cycle ;
320+ }
321+
322+ List <EnumTypeExtensionDefinition > extensions = typeRegistry .enumTypeExtensions ().getOrDefault (typeDefinition .getName (), Collections .emptyList ());
323+ for (EnumTypeExtensionDefinition extension : extensions ) {
324+ cycle = findCycleFromDirectives (extension .getDirectives (), startDirectiveName , path , visited );
325+ if (cycle != null ) {
326+ return cycle ;
327+ }
328+
329+ cycle = findCycleFromEnumValues (extension .getEnumValueDefinitions (), startDirectiveName , path , visited );
330+ if (cycle != null ) {
331+ return cycle ;
332+ }
333+ }
334+ return null ;
335+ }
336+
337+ private String findCycleFromEnumValues (List <EnumValueDefinition > enumValueDefinitions , String startDirectiveName , List <String > path , Set <String > visited ) {
338+ for (EnumValueDefinition enumValueDefinition : enumValueDefinitions ) {
339+ String cycle = findCycleFromDirectives (enumValueDefinition .getDirectives (), startDirectiveName , path , visited );
340+ if (cycle != null ) {
341+ return cycle ;
342+ }
343+ }
344+ return null ;
345+ }
346+
347+ private String findCycleFromInputObjectType (InputObjectTypeDefinition typeDefinition , String startDirectiveName , List <String > path , Set <String > visited ) {
348+ String cycle = findCycleFromDirectives (typeDefinition .getDirectives (), startDirectiveName , path , visited );
349+ if (cycle != null ) {
350+ return cycle ;
351+ }
352+
353+ cycle = findCycleFromInputValueDefinitions (typeDefinition .getInputValueDefinitions (), startDirectiveName , path , visited );
354+ if (cycle != null ) {
355+ return cycle ;
356+ }
357+
358+ List <InputObjectTypeExtensionDefinition > extensions = typeRegistry .inputObjectTypeExtensions ().getOrDefault (typeDefinition .getName (), Collections .emptyList ());
359+ for (InputObjectTypeExtensionDefinition extension : extensions ) {
360+ cycle = findCycleFromDirectives (extension .getDirectives (), startDirectiveName , path , visited );
361+ if (cycle != null ) {
362+ return cycle ;
363+ }
364+
365+ cycle = findCycleFromInputValueDefinitions (extension .getInputValueDefinitions (), startDirectiveName , path , visited );
366+ if (cycle != null ) {
367+ return cycle ;
368+ }
369+ }
370+ return null ;
371+ }
372+
373+ private List <String > addToPath (List <String > path , String element ) {
374+ List <String > nextPath = new ArrayList <>(path );
375+ nextPath .add (element );
376+ return nextPath ;
377+ }
378+
379+ private Set <String > addToVisited (Set <String > visited , String element ) {
380+ Set <String > nextVisited = new LinkedHashSet <>(visited );
381+ nextVisited .add (element );
382+ return nextVisited ;
383+ }
384+
385+ private String cyclePath (List <String > path , String cycleElement ) {
386+ return String .join (" -> " , addToPath (path , cycleElement ));
387+ }
388+
389+ private String directiveName (String directiveName ) {
390+ return "@" + directiveName ;
391+ }
392+
197393 private static void assertTypeName (NamedNode <?> node , List <GraphQLError > errors ) {
198394 if (node .getName ().length () >= 2 && node .getName ().startsWith ("__" )) {
199395 errors .add ((new IllegalNameError (node )));
@@ -224,4 +420,4 @@ private static TypeDefinition<?> findTypeDefFromRegistry(String typeName, TypeDe
224420 }
225421 return typeRegistry .scalars ().get (typeName );
226422 }
227- }
423+ }
0 commit comments