Skip to content

Commit a0096cb

Browse files
committed
graphql-java#457 - support for implicit schema when types are named Query
1 parent fcbce54 commit a0096cb

File tree

7 files changed

+291
-43
lines changed

7 files changed

+291
-43
lines changed

src/main/java/graphql/schema/SchemaUtil.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ public Map<String, GraphQLType> allTypes(GraphQLSchema schema, Set<GraphQLType>
154154
if (schema.isSupportingMutations()) {
155155
collectTypes(schema.getMutationType(), typesByName);
156156
}
157+
if (schema.isSupportingSubscriptions()) {
158+
collectTypes(schema.getSubscriptionType(), typesByName);
159+
}
157160
if (additionalTypes != null) {
158161
for (GraphQLType type : additionalTypes) {
159162
collectTypes(type, typesByName);

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

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import graphql.language.Type;
2424
import graphql.language.TypeDefinition;
2525
import graphql.language.TypeExtensionDefinition;
26+
import graphql.language.TypeName;
2627
import graphql.language.UnionTypeDefinition;
2728
import graphql.language.Value;
2829
import graphql.schema.DataFetcher;
@@ -128,11 +129,6 @@ void put(GraphQLInputType inputType) {
128129
RuntimeWiring getWiring() {
129130
return wiring;
130131
}
131-
132-
@SuppressWarnings("OptionalGetWithoutIsPresent")
133-
public SchemaDefinition getSchemaDefinition() {
134-
return typeRegistry.schemaDefinition().get();
135-
}
136132
}
137133

138134
private final SchemaTypeChecker typeChecker = new SchemaTypeChecker();
@@ -145,7 +141,9 @@ public SchemaGenerator() {
145141
*
146142
* @param typeRegistry this can be obtained via {@link SchemaParser#parse(String)}
147143
* @param wiring this can be built using {@link RuntimeWiring#newRuntimeWiring()}
144+
*
148145
* @return an executable schema
146+
*
149147
* @throws SchemaProblem if there are problems in assembling a schema such as missing type resolvers or no operations defined
150148
*/
151149
public GraphQLSchema makeExecutableSchema(TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) throws SchemaProblem {
@@ -159,32 +157,56 @@ public GraphQLSchema makeExecutableSchema(TypeDefinitionRegistry typeRegistry, R
159157
}
160158

161159
private GraphQLSchema makeExecutableSchemaImpl(BuildContext buildCtx) {
160+
GraphQLObjectType query;
161+
GraphQLObjectType mutation;
162+
GraphQLObjectType subscription;
162163

163-
SchemaDefinition schemaDefinition = buildCtx.getSchemaDefinition();
164-
List<OperationTypeDefinition> operationTypes = schemaDefinition.getOperationTypeDefinitions();
164+
GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema();
165165

166-
// pre-flight checked via checker
167-
@SuppressWarnings("OptionalGetWithoutIsPresent")
168-
OperationTypeDefinition queryOp = operationTypes.stream().filter(op -> "query".equals(op.getName())).findFirst().get();
169-
Optional<OperationTypeDefinition> mutationOp = operationTypes.stream().filter(op -> "mutation".equals(op.getName())).findFirst();
170-
Optional<OperationTypeDefinition> subscriptionOp = operationTypes.stream().filter(op -> "subscription".equals(op.getName())).findFirst();
166+
//
167+
// Schema can be missing if the type is called 'Query'. Pre flight checks have checked that!
168+
//
169+
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
170+
if (!typeRegistry.schemaDefinition().isPresent()) {
171+
@SuppressWarnings("OptionalGetWithoutIsPresent")
172+
TypeDefinition queryTypeDef = typeRegistry.getType("Query").get();
171173

172-
GraphQLObjectType query = buildOperation(buildCtx, queryOp);
173-
GraphQLObjectType mutation;
174-
GraphQLObjectType subscription;
174+
query = buildOutputType(buildCtx, new TypeName(queryTypeDef.getName()));
175+
schemaBuilder.query(query);
175176

176-
GraphQLSchema.Builder schemaBuilder = GraphQLSchema
177-
.newSchema()
178-
.query(query);
177+
Optional<TypeDefinition> mutationTypeDef = typeRegistry.getType("Mutation");
178+
if (mutationTypeDef.isPresent()) {
179+
mutation = buildOutputType(buildCtx, new TypeName(mutationTypeDef.get().getName()));
180+
schemaBuilder.mutation(mutation);
181+
}
182+
Optional<TypeDefinition> subscriptionTypeDef = typeRegistry.getType("Subscription");
183+
if (subscriptionTypeDef.isPresent()) {
184+
subscription = buildOutputType(buildCtx, new TypeName(subscriptionTypeDef.get().getName()));
185+
schemaBuilder.subscription(subscription);
186+
}
187+
} else {
188+
SchemaDefinition schemaDefinition = typeRegistry.schemaDefinition().get();
189+
List<OperationTypeDefinition> operationTypes = schemaDefinition.getOperationTypeDefinitions();
179190

180-
if (mutationOp.isPresent()) {
181-
mutation = buildOperation(buildCtx, mutationOp.get());
182-
schemaBuilder.mutation(mutation);
183-
}
184-
if (subscriptionOp.isPresent()) {
185-
subscription = buildOperation(buildCtx, subscriptionOp.get());
186-
schemaBuilder.subscription(subscription);
191+
// pre-flight checked via checker
192+
@SuppressWarnings("OptionalGetWithoutIsPresent")
193+
OperationTypeDefinition queryOp = operationTypes.stream().filter(op -> "query".equals(op.getName())).findFirst().get();
194+
Optional<OperationTypeDefinition> mutationOp = operationTypes.stream().filter(op -> "mutation".equals(op.getName())).findFirst();
195+
Optional<OperationTypeDefinition> subscriptionOp = operationTypes.stream().filter(op -> "subscription".equals(op.getName())).findFirst();
196+
197+
query = buildOperation(buildCtx, queryOp);
198+
schemaBuilder.query(query);
199+
200+
if (mutationOp.isPresent()) {
201+
mutation = buildOperation(buildCtx, mutationOp.get());
202+
schemaBuilder.mutation(mutation);
203+
}
204+
if (subscriptionOp.isPresent()) {
205+
subscription = buildOperation(buildCtx, subscriptionOp.get());
206+
schemaBuilder.subscription(subscription);
207+
}
187208
}
209+
188210
return schemaBuilder.build();
189211
}
190212

@@ -200,6 +222,7 @@ private GraphQLObjectType buildOperation(BuildContext buildCtx, OperationTypeDef
200222
*
201223
* @param buildCtx the context we need to work out what we are doing
202224
* @param rawType the type to be built
225+
*
203226
* @return an output type
204227
*/
205228
@SuppressWarnings("unchecked")
@@ -535,8 +558,8 @@ private TypeResolver getTypeResolverForInterface(BuildContext buildCtx, Interfac
535558
private String buildDescription(Node node) {
536559
List<Comment> comments = node.getComments();
537560
List<String> lines = new ArrayList<>();
538-
for (int i = 0; i < comments.size(); i++) {
539-
String commentLine = comments.get(i).getContent();
561+
for (Comment comment : comments) {
562+
String commentLine = comment.getContent();
540563
if (commentLine.trim().isEmpty()) {
541564
lines.clear();
542565
} else {

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public String print(GraphQLSchema schema) {
120120

121121
return sw.toString();
122122
}
123+
123124
private interface TypePrinter<T> {
124125

125126
void print(PrintWriter out, T type);
@@ -215,16 +216,38 @@ private TypePrinter<GraphQLInputObjectType> inputObjectPrinter() {
215216

216217
private TypePrinter<GraphQLSchema> schemaPrinter() {
217218
return (out, type) -> {
218-
out.format("schema {\n");
219219
GraphQLObjectType queryType = type.getQueryType();
220220
GraphQLObjectType mutationType = type.getMutationType();
221-
if (queryType != null) {
222-
out.format(" query : %s\n", queryType.getName());
221+
GraphQLObjectType subscriptionType = type.getSubscriptionType();
222+
223+
224+
// when serializing a GraphQL schema using the type system language, a
225+
// schema definition should be omitted if only uses the default root type names.
226+
boolean needsSchemaPrinted = false;
227+
228+
if (queryType != null && !queryType.getName().equals("Query")) {
229+
needsSchemaPrinted = true;
223230
}
224-
if (mutationType != null) {
225-
out.format(" mutation : %s\n", mutationType.getName());
231+
if (mutationType != null && !mutationType.getName().equals("Mutation")) {
232+
needsSchemaPrinted = true;
233+
}
234+
if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) {
235+
needsSchemaPrinted = true;
236+
}
237+
238+
if (needsSchemaPrinted) {
239+
out.format("schema {\n");
240+
if (queryType != null) {
241+
out.format(" query : %s\n", queryType.getName());
242+
}
243+
if (mutationType != null) {
244+
out.format(" mutation : %s\n", mutationType.getName());
245+
}
246+
if (subscriptionType != null) {
247+
out.format(" subscription : %s\n", subscriptionType.getName());
248+
}
249+
out.format("}\n\n");
226250
}
227-
out.format("}\n\n");
228251
};
229252
}
230253

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,17 @@ public List<GraphQLError> checkTypeRegistry(TypeDefinitionRegistry typeRegistry,
6464
}
6565

6666
private void checkSchemaInvariants(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry) {
67+
/*
68+
https://github.com/facebook/graphql/pull/90/files#diff-fe406b08746616e2f5f00909488cce66R1000
69+
70+
GraphQL type system definitions can omit the schema definition when the query
71+
and mutation root types are named `Query` and `Mutation`, respectively.
72+
*/
6773
// schema
6874
if (!typeRegistry.schemaDefinition().isPresent()) {
69-
errors.add(new SchemaMissingError());
75+
if (!typeRegistry.getType("Query").isPresent()) {
76+
errors.add(new SchemaMissingError());
77+
}
7078
} else {
7179
SchemaDefinition schemaDefinition = typeRegistry.schemaDefinition().get();
7280
List<OperationTypeDefinition> operationTypeDefinitions = schemaDefinition.getOperationTypeDefinitions();

src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import spock.lang.Specification
88

99
import java.util.function.UnaryOperator
1010

11+
import static graphql.Scalars.*
12+
1113
class SchemaGeneratorTest extends Specification {
1214

1315
def resolver = new TypeResolver() {
@@ -287,7 +289,7 @@ class SchemaGeneratorTest extends Specification {
287289

288290
def foobar = schema.getQueryType().getFieldDefinition("foobar")
289291
foobar.type instanceof GraphQLUnionType
290-
def types = ((GraphQLUnionType) foobar.type).getTypes();
292+
def types = ((GraphQLUnionType) foobar.type).getTypes()
291293
types.size() == 2
292294
types[0] instanceof GraphQLObjectType
293295
types[1] instanceof GraphQLObjectType
@@ -326,7 +328,7 @@ class SchemaGeneratorTest extends Specification {
326328

327329
def foobar = schema.getQueryType().getFieldDefinition("foobar")
328330
foobar.type instanceof GraphQLUnionType
329-
def types = ((GraphQLUnionType) foobar.type).getTypes();
331+
def types = ((GraphQLUnionType) foobar.type).getTypes()
330332
types.size() == 2
331333
types[0] instanceof GraphQLObjectType
332334
types[1] instanceof GraphQLObjectType
@@ -365,7 +367,7 @@ class SchemaGeneratorTest extends Specification {
365367

366368
def foobar = schema.getQueryType().getFieldDefinition("foobar")
367369
foobar.type instanceof GraphQLUnionType
368-
def types = ((GraphQLUnionType) foobar.type).getTypes();
370+
def types = ((GraphQLUnionType) foobar.type).getTypes()
369371
types.size() == 2
370372
types[0] instanceof GraphQLObjectType
371373
types[1] instanceof GraphQLObjectType
@@ -404,7 +406,7 @@ class SchemaGeneratorTest extends Specification {
404406

405407
def foobar = schema.getQueryType().getFieldDefinition("foobar")
406408
foobar.type instanceof GraphQLUnionType
407-
def types = ((GraphQLUnionType) foobar.type).getTypes();
409+
def types = ((GraphQLUnionType) foobar.type).getTypes()
408410
types.size() == 2
409411
types[0] instanceof GraphQLObjectType
410412
types[1] instanceof GraphQLObjectType
@@ -813,7 +815,7 @@ class SchemaGeneratorTest extends Specification {
813815
query: Query
814816
}
815817
"""
816-
def enumValuesProvider = new NaturalEnumValuesProvider<ExampleEnum>(ExampleEnum.class);
818+
def enumValuesProvider = new NaturalEnumValuesProvider<ExampleEnum>(ExampleEnum.class)
817819
when:
818820

819821
def wiring = RuntimeWiring.newRuntimeWiring()
@@ -855,4 +857,65 @@ class SchemaGeneratorTest extends Specification {
855857
enumType.getValue("C").value == "C"
856858

857859
}
860+
861+
862+
def "schema is optional if there is a type called Query"() {
863+
864+
def spec = """
865+
type Query {
866+
field : String
867+
}
868+
869+
type mutation { # case matters this is not an implicit mutation
870+
field : Int
871+
}
872+
873+
type subscription { # case matters this is not an implicit subscription
874+
field : Boolean
875+
}
876+
"""
877+
878+
def schema = generateSchema(spec, RuntimeWiring.newRuntimeWiring().build())
879+
880+
expect:
881+
882+
schema != null
883+
schema.getQueryType() != null
884+
schema.getMutationType() == null
885+
schema.getSubscriptionType() == null
886+
schema.getQueryType().getFieldDefinition("field").getType() == GraphQLString
887+
888+
}
889+
890+
def "schema is optional if there is a type called Query while Mutation and Subscription will be found"() {
891+
892+
def spec = """
893+
type Query {
894+
field : String
895+
}
896+
897+
type Mutation {
898+
field : Int
899+
}
900+
901+
type Subscription {
902+
field : Boolean
903+
}
904+
"""
905+
906+
def schema = generateSchema(spec, RuntimeWiring.newRuntimeWiring().build())
907+
908+
expect:
909+
910+
schema != null
911+
schema != null
912+
schema.getQueryType() != null
913+
schema.getMutationType() != null
914+
schema.getSubscriptionType() != null
915+
schema.getQueryType().getFieldDefinition("field").getType() == GraphQLString
916+
schema.getMutationType().getFieldDefinition("field").getType() == GraphQLInt
917+
schema.getSubscriptionType().getFieldDefinition("field").getType() == GraphQLBoolean
918+
919+
}
920+
858921
}

0 commit comments

Comments
 (0)