1313import graphql .schema .GraphQLObjectType ;
1414import graphql .schema .GraphQLSchema ;
1515import graphql .schema .GraphQLSchemaElement ;
16+ import graphql .schema .GraphQLType ;
1617import graphql .schema .GraphQLTypeVisitorStub ;
1718import graphql .schema .SchemaTransformer ;
1819import graphql .util .TraversalControl ;
2122import java .util .List ;
2223import java .util .Set ;
2324
25+ import static graphql .Assert .assertNotNull ;
2426import static graphql .Assert .assertShouldNeverHappen ;
2527import static graphql .Scalars .GraphQLString ;
2628import static graphql .introspection .Introspection .__EnumValue ;
5355 * type __Field {
5456 * name: String!
5557 * // other fields ...
56- * appliedDirectives: [__AppliedDirective !]! // NEW FIELD
58+ * appliedDirectives: [_AppliedDirective !]! // NEW FIELD
5759 * }
5860 *
59- * type __AppliedDirective { // NEW INTROSPECTION TYPE
61+ * type _AppliedDirective { // NEW INTROSPECTION TYPE
6062 * name: String!
61- * args: [__DirectiveArgument !]!
63+ * args: [_DirectiveArgument !]!
6264 * }
6365 *
64- * type __DirectiveArgument { // NEW INTROSPECTION TYPE
66+ * type _DirectiveArgument { // NEW INTROSPECTION TYPE
6567 * name: String!
6668 * value: String!
6769 * }
7173public class IntrospectionWithDirectivesSupport {
7274
7375 private final DirectivePredicate directivePredicate ;
76+ private final String typePrefix ;
7477
7578 private final Set <String > INTROSPECTION_ELEMENTS = ImmutableSet .of (
7679 __Schema .getName (), __Type .getName (), __Field .getName (), __EnumValue .getName (), __InputValue .getName ()
7780 );
7881
79- private final GraphQLObjectType __DirectiveArgument = newObject ().name ("__DirectiveArgument" )
80- .description ("Directive arguments can have names and values. The values are in graphql SDL syntax printed as a string." +
81- " This type is NOT specified by the graphql specification presently." )
82- .field (fld -> fld
83- .name ("name" )
84- .type (nonNull (GraphQLString )))
85- .field (fld -> fld
86- .name ("value" )
87- .type (nonNull (GraphQLString )))
88- .build ();
89-
90- private final GraphQLObjectType __AppliedDirective = newObject ().name ("__AppliedDirective" )
91- .description ("An Applied Directive is an instances of a directive as applied to a schema element." +
92- " This type is NOT specified by the graphql specification presently." )
93- .field (fld -> fld
94- .name ("name" )
95- .type (nonNull (GraphQLString )))
96- .field (fld -> fld
97- .name ("args" )
98- .type (nonNull (list (nonNull (__DirectiveArgument )))))
99- .build ();
10082
10183 /**
10284 * This version lists all directives on a schema element
@@ -111,7 +93,23 @@ public IntrospectionWithDirectivesSupport() {
11193 * @param directivePredicate the filtering predicate to decide what directives are shown
11294 */
11395 public IntrospectionWithDirectivesSupport (DirectivePredicate directivePredicate ) {
114- this .directivePredicate = directivePredicate ;
96+ this (directivePredicate , "_" );
97+ }
98+
99+ /**
100+ * This version allows you to filter what directives are listed via the provided predicate
101+ *
102+ * Some graphql systems (graphql-js in 2021) cannot cope with extra types starting with `__`
103+ * so we use a `_` as a prefx by default. You can supply your own prefix via this constructor.
104+ *
105+ * See: https://github.com/graphql-java/graphql-java/pull/2221 for more details
106+ *
107+ * @param directivePredicate the filtering predicate to decide what directives are shown
108+ * @param typePrefix the prefix to put on the new `AppliedDirectives` type
109+ */
110+ public IntrospectionWithDirectivesSupport (DirectivePredicate directivePredicate , String typePrefix ) {
111+ this .directivePredicate = assertNotNull (directivePredicate );
112+ this .typePrefix = assertNotNull (typePrefix );
115113 }
116114
117115 /**
@@ -122,32 +120,71 @@ public IntrospectionWithDirectivesSupport(DirectivePredicate directivePredicate)
122120 * @return the transformed schema with new extension types and fields in it for Introspection
123121 */
124122 public GraphQLSchema apply (GraphQLSchema schema ) {
123+ GraphQLObjectType directiveArgumentType = mkDirectiveArgumentType (typePrefix + "DirectiveArgument" );
124+ GraphQLObjectType appliedDirectiveType = mkAppliedDirectiveType (typePrefix + "AppliedDirective" , directiveArgumentType );
125125 GraphQLTypeVisitorStub visitor = new GraphQLTypeVisitorStub () {
126126 @ Override
127127 public TraversalControl visitGraphQLObjectType (GraphQLObjectType objectType , TraverserContext <GraphQLSchemaElement > context ) {
128128 GraphQLCodeRegistry .Builder codeRegistry = context .getVarFromParents (GraphQLCodeRegistry .Builder .class );
129129 // we need to change __XXX introspection types to have directive extensions
130130 if (INTROSPECTION_ELEMENTS .contains (objectType .getName ())) {
131- GraphQLObjectType newObjectType = addAppliedDirectives (objectType , codeRegistry );
131+ GraphQLObjectType newObjectType = addAppliedDirectives (objectType , codeRegistry , appliedDirectiveType , directiveArgumentType );
132132 return changeNode (context , newObjectType );
133133 }
134134 return CONTINUE ;
135135 }
136136 };
137- return SchemaTransformer .transformSchema (schema , visitor );
137+ schema = SchemaTransformer .transformSchema (schema , visitor );
138+ return addDirectiveDefinitionFilter (schema );
139+ }
140+
141+ private GraphQLObjectType mkDirectiveArgumentType (String name ) {
142+ return newObject ().name (name )
143+ .description ("Directive arguments can have names and values. The values are in graphql SDL syntax printed as a string." +
144+ " This type is NOT specified by the graphql specification presently." )
145+ .field (fld -> fld
146+ .name ("name" )
147+ .type (nonNull (GraphQLString )))
148+ .field (fld -> fld
149+ .name ("value" )
150+ .type (nonNull (GraphQLString )))
151+ .build ();
152+ }
153+
154+ private GraphQLObjectType mkAppliedDirectiveType (String name , GraphQLType directiveArgumentType ) {
155+ return newObject ().name (name )
156+ .description ("An Applied Directive is an instances of a directive as applied to a schema element." +
157+ " This type is NOT specified by the graphql specification presently." )
158+ .field (fld -> fld
159+ .name ("name" )
160+ .type (nonNull (GraphQLString )))
161+ .field (fld -> fld
162+ .name ("args" )
163+ .type (nonNull (list (nonNull (directiveArgumentType )))))
164+ .build ();
138165 }
139166
140- private GraphQLObjectType addAppliedDirectives (GraphQLObjectType originalType , GraphQLCodeRegistry .Builder codeRegistry ) {
141- GraphQLObjectType objectType = originalType .transform (bld -> bld .field (fld -> fld .name ("appliedDirectives" ).type (nonNull (list (nonNull (__AppliedDirective ))))));
167+ private GraphQLSchema addDirectiveDefinitionFilter (GraphQLSchema schema ) {
168+ DataFetcher <?> df = env -> {
169+ List <GraphQLDirective > definedDirectives = env .getGraphQLSchema ().getDirectives ();
170+ return filterDirectives (true , null , definedDirectives );
171+ };
172+ GraphQLCodeRegistry codeRegistry = schema .getCodeRegistry ().transform (bld ->
173+ bld .dataFetcher (coordinates (__Schema , "directives" ), df ));
174+ return schema .transform (bld -> bld .codeRegistry (codeRegistry ));
175+ }
176+
177+ private GraphQLObjectType addAppliedDirectives (GraphQLObjectType originalType , GraphQLCodeRegistry .Builder codeRegistry , GraphQLObjectType appliedDirectiveType , GraphQLObjectType directiveArgumentType ) {
178+ GraphQLObjectType objectType = originalType .transform (bld -> bld .field (fld -> fld .name ("appliedDirectives" ).type (nonNull (list (nonNull (appliedDirectiveType ))))));
142179 DataFetcher <?> df = env -> {
143180 Object source = env .getSource ();
144181 if (source instanceof GraphQLDirectiveContainer ) {
145182 GraphQLDirectiveContainer type = env .getSource ();
146- return filterDirectives (type , type .getDirectives ());
183+ return filterDirectives (false , type , type .getDirectives ());
147184 }
148185 if (source instanceof GraphQLSchema ) {
149186 GraphQLSchema schema = (GraphQLSchema ) source ;
150- return filterDirectives (null , schema .getSchemaDirectives ());
187+ return filterDirectives (true , null , schema .getSchemaDirectives ());
151188 }
152189 return assertShouldNeverHappen ("What directive containing element have we not considered? - %s" , originalType );
153190 };
@@ -161,19 +198,24 @@ private GraphQLObjectType addAppliedDirectives(GraphQLObjectType originalType, G
161198 return AstPrinter .printAst (AstValueHelper .astFromValue (value , argument .getType ()));
162199 };
163200 codeRegistry .dataFetcher (coordinates (objectType , "appliedDirectives" ), df );
164- codeRegistry .dataFetcher (coordinates (__AppliedDirective , "args" ), argsDF );
165- codeRegistry .dataFetcher (coordinates (__DirectiveArgument , "value" ), argValueDF );
201+ codeRegistry .dataFetcher (coordinates (appliedDirectiveType , "args" ), argsDF );
202+ codeRegistry .dataFetcher (coordinates (directiveArgumentType , "value" ), argValueDF );
166203 return objectType ;
167204 }
168205
169- private List <GraphQLDirective > filterDirectives (GraphQLDirectiveContainer container , List <GraphQLDirective > directives ) {
206+ private List <GraphQLDirective > filterDirectives (boolean isDefinedDirective , GraphQLDirectiveContainer container , List <GraphQLDirective > directives ) {
170207 return directives .stream ().filter (directive -> {
171208 DirectivePredicateEnvironment env = new DirectivePredicateEnvironment () {
172209 @ Override
173210 public GraphQLDirectiveContainer getDirectiveContainer () {
174211 return container ;
175212 }
176213
214+ @ Override
215+ public boolean isDefinedDirective () {
216+ return isDefinedDirective ;
217+ }
218+
177219 @ Override
178220 public GraphQLDirective getDirective () {
179221 return directive ;
@@ -201,6 +243,16 @@ interface DirectivePredicateEnvironment {
201243 * @return the directive to be included
202244 */
203245 GraphQLDirective getDirective ();
246+
247+ /**
248+ * A schema has two list of directives. A list of directives that are defined
249+ * in that schema and the list of direcives that are applied to a schema element.
250+ *
251+ * This returns true if this filtering represents the defined directives.
252+ *
253+ * @return true if this is filtering defined directives
254+ */
255+ boolean isDefinedDirective ();
204256 }
205257
206258
0 commit comments