Skip to content

Commit b510f5e

Browse files
authored
This allows system data fetchers to be replaced (#2236)
* This allows system data fetchers to be replaced * Added the ability to filter defined directives via the ability to replace them * Support for adding prefixes to the types
1 parent 2231493 commit b510f5e

8 files changed

Lines changed: 334 additions & 44 deletions

File tree

src/main/java/graphql/introspection/Introspection.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ private static void register(GraphQLFieldsContainer parentType, String fieldName
6666
@Internal
6767
public static void addCodeForIntrospectionTypes(GraphQLCodeRegistry.Builder codeRegistry) {
6868
// place the system __ fields into the mix. They have no parent types
69-
codeRegistry.systemDataFetcher(systemCoordinates(SchemaMetaFieldDef.getName()), SchemaMetaFieldDefDataFetcher);
70-
codeRegistry.systemDataFetcher(systemCoordinates(TypeNameMetaFieldDef.getName()), TypeNameMetaFieldDefDataFetcher);
71-
codeRegistry.systemDataFetcher(systemCoordinates(TypeMetaFieldDef.getName()), TypeMetaFieldDefDataFetcher);
69+
codeRegistry.dataFetcherIfAbsent(systemCoordinates(SchemaMetaFieldDef.getName()), SchemaMetaFieldDefDataFetcher);
70+
codeRegistry.dataFetcherIfAbsent(systemCoordinates(TypeNameMetaFieldDef.getName()), TypeNameMetaFieldDefDataFetcher);
71+
codeRegistry.dataFetcherIfAbsent(systemCoordinates(TypeMetaFieldDef.getName()), TypeMetaFieldDefDataFetcher);
7272

73-
introspectionDataFetchers.forEach(codeRegistry::dataFetcher);
73+
introspectionDataFetchers.forEach(codeRegistry::dataFetcherIfAbsent);
7474
}
7575

7676

src/main/java/graphql/introspection/IntrospectionWithDirectivesSupport.java

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import graphql.schema.GraphQLObjectType;
1414
import graphql.schema.GraphQLSchema;
1515
import graphql.schema.GraphQLSchemaElement;
16+
import graphql.schema.GraphQLType;
1617
import graphql.schema.GraphQLTypeVisitorStub;
1718
import graphql.schema.SchemaTransformer;
1819
import graphql.util.TraversalControl;
@@ -21,6 +22,7 @@
2122
import java.util.List;
2223
import java.util.Set;
2324

25+
import static graphql.Assert.assertNotNull;
2426
import static graphql.Assert.assertShouldNeverHappen;
2527
import static graphql.Scalars.GraphQLString;
2628
import static graphql.introspection.Introspection.__EnumValue;
@@ -53,15 +55,15 @@
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
* }
@@ -71,32 +73,12 @@
7173
public 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

src/main/java/graphql/schema/FieldCoordinates.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public String getFieldName() {
3333
return fieldName;
3434
}
3535

36+
public boolean isSystemCoordinates() {
37+
return systemCoordinates;
38+
}
3639

3740
/**
3841
* Checks the validity of the field coordinate names. The validity checks vary by coordinate type. Standard

0 commit comments

Comments
 (0)