Skip to content

Commit 86da667

Browse files
author
Raymie Stata
committed
Merge remote-tracking branch 'upstream/master' into fastbuilder
2 parents eb8e24c + 7ba1647 commit 86da667

17 files changed

+259
-140
lines changed

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,11 @@ tasks.register('testWithJava21', Test) {
370370
javaLauncher = javaToolchains.launcherFor {
371371
languageVersion = JavaLanguageVersion.of(21)
372372
}
373+
testClassesDirs = sourceSets.test.output.classesDirs
374+
classpath = sourceSets.test.runtimeClasspath
375+
classpath += sourceSets.jmh.output
376+
dependsOn "jmhClasses"
377+
dependsOn tasks.named('testClasses')
373378
}
374379

375380
tasks.register('testWithJava17', Test) {

src/main/java/graphql/Directives.java

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
import graphql.language.StringValue;
88
import graphql.schema.GraphQLDirective;
99

10-
import java.util.List;
10+
import java.util.Collections;
11+
import java.util.LinkedHashMap;
12+
import java.util.LinkedHashSet;
13+
import java.util.Map;
14+
import java.util.Set;
1115
import java.util.concurrent.atomic.AtomicBoolean;
1216

1317
import static graphql.Scalars.GraphQLBoolean;
@@ -251,32 +255,54 @@ public class Directives {
251255
.build();
252256

253257
/**
254-
* Returns the directives that are included in a schema by default but can be removed
255-
* by calling {@code clearDirectives()} on the builder.
258+
* The set of all built-in directives that are always present in a graphql schema.
259+
* The iteration order is stable and meaningful.
260+
*/
261+
public static final Set<GraphQLDirective> BUILT_IN_DIRECTIVES;
262+
263+
/**
264+
* A map from directive name to directive for all built-in directives.
265+
*/
266+
public static final Map<String, GraphQLDirective> BUILT_IN_DIRECTIVES_MAP;
267+
268+
static {
269+
LinkedHashSet<GraphQLDirective> directives = new LinkedHashSet<>();
270+
directives.add(IncludeDirective);
271+
directives.add(SkipDirective);
272+
directives.add(DeprecatedDirective);
273+
directives.add(SpecifiedByDirective);
274+
directives.add(OneOfDirective);
275+
directives.add(DeferDirective);
276+
directives.add(ExperimentalDisableErrorPropagationDirective);
277+
BUILT_IN_DIRECTIVES = Collections.unmodifiableSet(directives);
278+
279+
LinkedHashMap<String, GraphQLDirective> map = new LinkedHashMap<>();
280+
for (GraphQLDirective d : BUILT_IN_DIRECTIVES) {
281+
map.put(d.getName(), d);
282+
}
283+
BUILT_IN_DIRECTIVES_MAP = Collections.unmodifiableMap(map);
284+
}
285+
286+
/**
287+
* Returns true if a directive with the provided name is a built-in directive.
288+
*
289+
* @param directiveName the name of the directive in question
256290
*
257-
* @return an unmodifiable list of default directives (include, skip)
291+
* @return true if the directive is built-in, false otherwise
258292
*/
259-
@Internal
260-
public static List<GraphQLDirective> getDefaultDirectives() {
261-
return List.of(IncludeDirective, SkipDirective);
293+
public static boolean isBuiltInDirective(String directiveName) {
294+
return BUILT_IN_DIRECTIVES_MAP.containsKey(directiveName);
262295
}
263296

264297
/**
265-
* Returns the directives that are mandatory and will always be added to a schema,
266-
* even after {@code clearDirectives()} is called on the builder.
267-
* These are inherently part of the GraphQL spec.
298+
* Returns true if the provided directive is a built-in directive.
299+
*
300+
* @param directive the directive in question
268301
*
269-
* @return an unmodifiable list of mandatory directives
302+
* @return true if the directive is built-in, false otherwise
270303
*/
271-
@Internal
272-
public static List<GraphQLDirective> getMandatoryDirectives() {
273-
return List.of(
274-
DeprecatedDirective,
275-
SpecifiedByDirective,
276-
OneOfDirective,
277-
DeferDirective,
278-
ExperimentalDisableErrorPropagationDirective
279-
);
304+
public static boolean isBuiltInDirective(GraphQLDirective directive) {
305+
return isBuiltInDirective(directive.getName());
280306
}
281307

282308
private static Description createDescription(String s) {

src/main/java/graphql/introspection/IntrospectionResultToSchema.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import static graphql.Assert.assertShouldNeverHappen;
4242
import static graphql.Assert.assertTrue;
4343
import static graphql.collect.ImmutableKit.map;
44-
import static graphql.schema.idl.DirectiveInfo.isGraphqlSpecifiedDirective;
44+
import static graphql.Directives.isBuiltInDirective;
4545

4646
@SuppressWarnings("unchecked")
4747
@PublicApi
@@ -131,7 +131,7 @@ public Document createSchemaDefinition(Map<String, Object> introspectionResult)
131131

132132
private DirectiveDefinition createDirective(Map<String, Object> input) {
133133
String directiveName = (String) input.get("name");
134-
if (isGraphqlSpecifiedDirective(directiveName)) {
134+
if (isBuiltInDirective(directiveName)) {
135135
return null;
136136
}
137137

src/main/java/graphql/normalized/ExecutableNormalizedField.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,13 @@ public void forEachFieldDefinition(GraphQLSchema schema, Consumer<GraphQLFieldDe
198198
return;
199199
}
200200

201+
var fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
201202
for (String objectTypeName : objectTypeNames) {
202203
GraphQLObjectType type = (GraphQLObjectType) assertNotNull(schema.getType(objectTypeName));
203-
consumer.accept(assertNotNull(type.getField(fieldName), "No field %s found for type %s", fieldName, objectTypeName));
204+
// Use field visibility to allow custom visibility implementations to provide placeholder fields
205+
// for fields that don't exist on the local schema (e.g., in federated subgraphs)
206+
GraphQLFieldDefinition field = fieldVisibility.getFieldDefinition(type, fieldName);
207+
consumer.accept(assertNotNull(field, "No field %s found for type %s", fieldName, objectTypeName));
204208
}
205209
}
206210

@@ -223,7 +227,8 @@ private GraphQLFieldDefinition getOneFieldDefinition(GraphQLSchema schema) {
223227

224228
String objectTypeName = objectTypeNames.iterator().next();
225229
GraphQLObjectType type = (GraphQLObjectType) assertNotNull(schema.getType(objectTypeName));
226-
return assertNotNull(type.getField(fieldName), "No field %s found for type %s", fieldName, objectTypeName);
230+
var fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
231+
return assertNotNull(fieldVisibility.getFieldDefinition(type, fieldName), "No field %s found for type %s", fieldName, objectTypeName);
227232
}
228233

229234
private static GraphQLFieldDefinition resolveIntrospectionField(GraphQLSchema schema, Set<String> objectTypeNames, String fieldName) {

src/main/java/graphql/schema/GraphQLSchema.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,6 @@ public static Builder newSchema(GraphQLSchema existingSchema) {
762762
.introspectionSchemaType(existingSchema.getIntrospectionSchemaType())
763763
.codeRegistry(existingSchema.getCodeRegistry())
764764
.clearAdditionalTypes()
765-
.clearDirectives()
766765
.additionalDirectives(new LinkedHashSet<>(existingSchema.getDirectives()))
767766
.clearSchemaDirectives()
768767
.withSchemaDirectives(schemaDirectivesArray(existingSchema))
@@ -811,11 +810,7 @@ public static class Builder {
811810
private List<SchemaExtensionDefinition> extensionDefinitions;
812811
private String description;
813812

814-
// We initially add these default directives (e.g., include and skip), but these can be
815-
// cleared by the user (unlike mandatory ones which are always re-added in buildImpl)
816-
private final Set<GraphQLDirective> additionalDirectives = new LinkedHashSet<>(
817-
Directives.getDefaultDirectives()
818-
);
813+
private final Set<GraphQLDirective> additionalDirectives = new LinkedHashSet<>();
819814
private final Set<GraphQLNamedType> additionalTypes = new LinkedHashSet<>();
820815
private final List<GraphQLDirective> schemaDirectives = new ArrayList<>();
821816
private final List<GraphQLAppliedDirective> schemaAppliedDirectives = new ArrayList<>();
@@ -933,12 +928,6 @@ public Builder additionalDirective(GraphQLDirective additionalDirective) {
933928
return this;
934929
}
935930

936-
public Builder clearDirectives() {
937-
this.additionalDirectives.clear();
938-
return this;
939-
}
940-
941-
942931
public Builder withSchemaDirectives(GraphQLDirective... directives) {
943932
for (GraphQLDirective directive : directives) {
944933
withSchemaDirective(directive);
@@ -1031,8 +1020,8 @@ private GraphQLSchema buildImpl() {
10311020
assertNotNull(additionalTypes, "additionalTypes can't be null");
10321021
assertNotNull(additionalDirectives, "additionalDirectives can't be null");
10331022

1034-
// Mandatory directives are always added, even after clearDirectives() - they're part of the spec
1035-
Directives.getMandatoryDirectives().forEach(d -> addBuiltInDirective(d, additionalDirectives));
1023+
// built-in directives are always present in a schema and come first
1024+
ensureBuiltInDirectives();
10361025

10371026
// quick build - no traversing
10381027
final GraphQLSchema partiallyBuiltSchema = new GraphQLSchema(this);
@@ -1055,6 +1044,23 @@ private GraphQLSchema buildImpl() {
10551044
return validateSchema(finalSchema);
10561045
}
10571046

1047+
private void ensureBuiltInDirectives() {
1048+
// put built-in directives first, preserving user-supplied overrides by name
1049+
Set<String> userDirectiveNames = new LinkedHashSet<>();
1050+
for (GraphQLDirective d : additionalDirectives) {
1051+
userDirectiveNames.add(d.getName());
1052+
}
1053+
LinkedHashSet<GraphQLDirective> ordered = new LinkedHashSet<>();
1054+
for (GraphQLDirective builtIn : Directives.BUILT_IN_DIRECTIVES) {
1055+
if (!userDirectiveNames.contains(builtIn.getName())) {
1056+
ordered.add(builtIn);
1057+
}
1058+
}
1059+
ordered.addAll(additionalDirectives);
1060+
additionalDirectives.clear();
1061+
additionalDirectives.addAll(ordered);
1062+
}
1063+
10581064
private GraphQLSchema validateSchema(GraphQLSchema graphQLSchema) {
10591065
Collection<SchemaValidationError> errors = new SchemaValidator().validateSchema(graphQLSchema);
10601066
if (!errors.isEmpty()) {
@@ -1363,6 +1369,9 @@ public GraphQLSchema build() {
13631369
shallowTypeRefCollector.replaceTypes(typeMap);
13641370

13651371
// Step 2: Add built-in directives if missing
1372+
Directives.BUILT_IN_DIRECTIVES.forEach(this::addDirectiveIfMissing);
1373+
1374+
13661375
addBuiltInDirectivesIfMissing();
13671376

13681377
// Step 3: Create schema via private constructor
@@ -1379,11 +1388,6 @@ public GraphQLSchema build() {
13791388
return schema;
13801389
}
13811390

1382-
private void addBuiltInDirectivesIfMissing() {
1383-
Directives.getDefaultDirectives().forEach(this::addDirectiveIfMissing);
1384-
Directives.getMandatoryDirectives().forEach(this::addDirectiveIfMissing);
1385-
}
1386-
13871391
private void addDirectiveIfMissing(GraphQLDirective directive) {
13881392
if (!directiveMap.containsKey(directive.getName())) {
13891393
directiveMap.put(directive.getName(), directive);

src/main/java/graphql/schema/GraphQLTypeUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import graphql.Assert;
44
import graphql.PublicApi;
55
import graphql.introspection.Introspection;
6-
import graphql.schema.idl.DirectiveInfo;
6+
import graphql.Directives;
77
import graphql.schema.idl.ScalarInfo;
88

99
import java.util.Stack;
@@ -293,7 +293,7 @@ public static Predicate<GraphQLNamedSchemaElement> isSystemElement() {
293293
return ScalarInfo.isGraphqlSpecifiedScalar((GraphQLScalarType) schemaElement);
294294
}
295295
if (schemaElement instanceof GraphQLDirective) {
296-
return DirectiveInfo.isGraphqlSpecifiedDirective((GraphQLDirective) schemaElement);
296+
return Directives.isBuiltInDirective((GraphQLDirective) schemaElement);
297297
}
298298
if (schemaElement instanceof GraphQLNamedType) {
299299
return Introspection.isIntrospectionTypes((GraphQLNamedType) schemaElement);

src/main/java/graphql/schema/diffing/SchemaGraphFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import graphql.introspection.Introspection;
77
import graphql.language.AstPrinter;
88
import graphql.schema.*;
9-
import graphql.schema.idl.DirectiveInfo;
9+
import graphql.Directives;
1010
import graphql.schema.idl.ScalarInfo;
1111
import graphql.util.TraversalControl;
1212
import graphql.util.Traverser;
@@ -390,7 +390,7 @@ private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) {
390390
directiveVertex.add("name", directive.getName());
391391
directiveVertex.add("repeatable", directive.isRepeatable());
392392
directiveVertex.add("locations", directive.validLocations());
393-
boolean graphqlSpecified = DirectiveInfo.isGraphqlSpecifiedDirective(directive.getName());
393+
boolean graphqlSpecified = Directives.isBuiltInDirective(directive.getName());
394394
directiveVertex.setBuiltInType(graphqlSpecified);
395395
directiveVertex.add("description", desc(directive.getDescription()));
396396
for (GraphQLArgument argument : directive.getArguments()) {

src/main/java/graphql/schema/idl/DirectiveInfo.java

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/main/java/graphql/schema/idl/SchemaPrinter.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.schema.idl;
22

33
import graphql.Assert;
4+
import graphql.Directives;
45
import graphql.DirectivesUtil;
56
import graphql.GraphQLContext;
67
import graphql.PublicApi;
@@ -64,6 +65,7 @@
6465
import java.util.stream.Stream;
6566

6667
import static graphql.Directives.DeprecatedDirective;
68+
import static graphql.Directives.SpecifiedByDirective;
6769
import static graphql.Scalars.GraphQLString;
6870
import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY;
6971
import static graphql.util.EscapeUtil.escapeJsonString;
@@ -80,7 +82,7 @@ public class SchemaPrinter {
8082
* This predicate excludes all directives which are specified by the GraphQL Specification.
8183
* Printing these directives is optional.
8284
*/
83-
public static final Predicate<String> ExcludeGraphQLSpecifiedDirectivesPredicate = d -> !DirectiveInfo.isGraphqlSpecifiedDirective(d);
85+
public static final Predicate<String> ExcludeGraphQLSpecifiedDirectivesPredicate = d -> !Directives.isBuiltInDirective(d);
8486

8587
/**
8688
* Options to use when printing a schema
@@ -559,7 +561,12 @@ private SchemaElementPrinter<GraphQLScalarType> scalarPrinter() {
559561
printAsAst(out, type.getDefinition(), type.getExtensionDefinitions());
560562
} else {
561563
printComments(out, type, "");
562-
out.format("scalar %s%s\n\n", type.getName(), directivesString(GraphQLScalarType.class, type));
564+
List<GraphQLAppliedDirective> directives = DirectivesUtil.toAppliedDirectives(type).stream()
565+
.filter(d -> !d.getName().equals(SpecifiedByDirective.getName()))
566+
.collect(toList());
567+
out.format("scalar %s%s%s\n\n", type.getName(),
568+
directivesString(GraphQLScalarType.class, directives),
569+
specifiedByUrlString(type));
563570
}
564571
}
565572
};
@@ -1103,6 +1110,14 @@ private String getDeprecationReason(GraphQLDirectiveContainer directiveContainer
11031110
}
11041111
}
11051112

1113+
private String specifiedByUrlString(GraphQLScalarType scalarType) {
1114+
String url = scalarType.getSpecifiedByUrl();
1115+
if (url == null || !options.getIncludeDirective().test(SpecifiedByDirective.getName())) {
1116+
return "";
1117+
}
1118+
return " @specifiedBy(url : \"" + escapeJsonString(url) + "\")";
1119+
}
1120+
11061121
private String directiveDefinition(GraphQLDirective directive) {
11071122
StringBuilder sb = new StringBuilder();
11081123

0 commit comments

Comments
 (0)