Skip to content

Commit afac3f9

Browse files
authored
Merge branch 'master' into bugfix/367-list-ids-not-resolved-correctly
2 parents 3262bc3 + 62b823b commit afac3f9

File tree

4 files changed

+136
-10
lines changed

4 files changed

+136
-10
lines changed

src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,30 @@ internal class SchemaClassScanner(
7777
do {
7878
do {
7979
// Require all implementors of discovered interfaces to be discovered or provided.
80-
handleInterfaceOrUnionSubTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
80+
handleDictionaryTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
8181
} while (scanQueue())
8282

8383
// Require all members of discovered unions to be discovered.
84-
handleInterfaceOrUnionSubTypes(getAllObjectTypeMembersOfDiscoveredUnions()) { "Object type '${it.name}' is a member of a known union, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
84+
handleDictionaryTypes(getAllObjectTypeMembersOfDiscoveredUnions()) { "Object type '${it.name}' is a member of a known union, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
8585
} while (scanQueue())
8686

87+
// Find unused types and include them if required
88+
if (options.includeUnusedTypes) {
89+
do {
90+
val unusedDefinitions = (definitionsByName.values - (dictionary.keys.toSet() + unvalidatedTypes))
91+
.filter { definition -> definition.name != "PageInfo" }
92+
.filterIsInstance<ObjectTypeDefinition>().distinct()
93+
94+
if (unusedDefinitions.isEmpty()) {
95+
break
96+
}
97+
98+
val unusedDefinition = unusedDefinitions.first()
99+
100+
handleDictionaryTypes(listOf(unusedDefinition)) { "Object type '${it.name}' is unused and includeUnusedTypes is true. Please pass a class for type '${it.name}' in the parser's dictionary." }
101+
} while (scanQueue())
102+
}
103+
87104
return validateAndCreateResult(rootTypeHolder)
88105
}
89106

@@ -208,7 +225,7 @@ internal class SchemaClassScanner(
208225
}.flatten().distinct()
209226
}
210227

211-
private fun handleInterfaceOrUnionSubTypes(types: List<ObjectTypeDefinition>, failureMessage: (ObjectTypeDefinition) -> String) {
228+
private fun handleDictionaryTypes(types: List<ObjectTypeDefinition>, failureMessage: (ObjectTypeDefinition) -> String) {
212229
types.forEach { type ->
213230
val dictionaryContainsType = dictionary.filter { it.key.name == type.name }.isNotEmpty()
214231
if (!unvalidatedTypes.contains(type) && !dictionaryContainsType) {

src/main/kotlin/graphql/kickstart/tools/SchemaParserOptions.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ data class SchemaParserOptions internal constructor(
2929
val introspectionEnabled: Boolean,
3030
val coroutineContextProvider: CoroutineContextProvider,
3131
val typeDefinitionFactories: List<TypeDefinitionFactory>,
32-
val fieldVisibility: GraphqlFieldVisibility?
32+
val fieldVisibility: GraphqlFieldVisibility?,
33+
val includeUnusedTypes: Boolean
3334
) {
3435
companion object {
3536
@JvmStatic
@@ -56,6 +57,7 @@ data class SchemaParserOptions internal constructor(
5657
private var coroutineContextProvider: CoroutineContextProvider? = null
5758
private var typeDefinitionFactories: MutableList<TypeDefinitionFactory> = mutableListOf(RelayConnectionFactory())
5859
private var fieldVisibility: GraphqlFieldVisibility? = null
60+
private var includeUnusedTypes = false
5961

6062
fun contextClass(contextClass: Class<*>) = this.apply {
6163
this.contextClass = contextClass
@@ -125,6 +127,10 @@ data class SchemaParserOptions internal constructor(
125127
this.fieldVisibility = fieldVisibility
126128
}
127129

130+
fun includeUnusedTypes(includeUnusedTypes: Boolean) = this.apply {
131+
this.includeUnusedTypes = includeUnusedTypes
132+
}
133+
128134
@ExperimentalCoroutinesApi
129135
fun build(): SchemaParserOptions {
130136
val coroutineContextProvider = coroutineContextProvider
@@ -162,7 +168,8 @@ data class SchemaParserOptions internal constructor(
162168
introspectionEnabled,
163169
coroutineContextProvider,
164170
typeDefinitionFactories,
165-
fieldVisibility
171+
fieldVisibility,
172+
includeUnusedTypes
166173
)
167174
}
168175
}

src/test/groovy/graphql/kickstart/tools/SchemaClassScannerSpec.groovy

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package graphql.kickstart.tools
22

3-
import graphql.language.InputObjectTypeDefinition
4-
import graphql.language.InputObjectTypeExtensionDefinition
5-
import graphql.language.InterfaceTypeDefinition
6-
import graphql.language.ObjectTypeDefinition
7-
import graphql.language.ScalarTypeDefinition
3+
import graphql.language.*
84
import graphql.schema.Coercing
95
import graphql.schema.GraphQLScalarType
106
import spock.lang.Specification
@@ -409,4 +405,108 @@ class SchemaClassScannerSpec extends Specification {
409405
String id
410406
}
411407
}
408+
409+
def "scanner should handle unused types when option is true"() {
410+
when:
411+
ScannedSchemaObjects objects = SchemaParser.newParser()
412+
.schemaString('''
413+
# Let's say this is the Products service from Apollo Federation Introduction
414+
415+
type Query {
416+
allProducts: [Product]
417+
}
418+
419+
type Product {
420+
name: String
421+
}
422+
423+
# these directives are defined in the Apollo Federation Specification:
424+
# https://www.apollographql.com/docs/apollo-server/federation/federation-spec/
425+
type User @key(fields: "id") @extends {
426+
id: ID! @external
427+
recentPurchasedProducts: [Product]
428+
address: Address
429+
}
430+
431+
type Address {
432+
street: String
433+
}
434+
''')
435+
.resolvers(new GraphQLQueryResolver() {
436+
List<Product> allProducts() { null }
437+
})
438+
.options(SchemaParserOptions.newOptions().includeUnusedTypes(true).build())
439+
.dictionary(User)
440+
.scan()
441+
442+
then:
443+
objects.definitions.find { it.name == "User" } != null
444+
objects.definitions.find { it.name == "Address" } != null
445+
}
446+
447+
class Product {
448+
String name
449+
}
450+
451+
class User {
452+
String id
453+
List<Product> recentPurchasedProducts
454+
Address address
455+
}
456+
457+
class Address {
458+
String street
459+
}
460+
461+
def "scanner should handle unused types with interfaces when option is true"() {
462+
when:
463+
ScannedSchemaObjects objects = SchemaParser.newParser()
464+
.schemaString('''
465+
type Query {
466+
whatever: Whatever
467+
}
468+
469+
type Whatever {
470+
value: String
471+
}
472+
473+
type Unused {
474+
someInterface: SomeInterface
475+
}
476+
477+
interface SomeInterface {
478+
value: String
479+
}
480+
481+
type Implementation implements SomeInterface {
482+
value: String
483+
}
484+
''')
485+
.resolvers(new GraphQLQueryResolver() {
486+
Whatever whatever() { null }
487+
})
488+
.options(SchemaParserOptions.newOptions().includeUnusedTypes(true).build())
489+
.dictionary(Unused, Implementation)
490+
.scan()
491+
492+
then:
493+
objects.definitions.find { it.name == "Unused" } != null
494+
objects.definitions.find { it.name == "SomeInterface" } != null
495+
objects.definitions.find { it.name == "Implementation" } != null
496+
}
497+
498+
class Whatever {
499+
String value
500+
}
501+
502+
class Unused {
503+
SomeInterface someInterface
504+
}
505+
506+
class Implementation implements SomeInterface {
507+
@Override
508+
String getValue() {
509+
return null
510+
}
511+
}
412512
}

src/test/groovy/graphql/kickstart/tools/TestInterfaces.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ interface Vehicle {
99
}
1010

1111
interface VehicleInformation {}
12+
13+
interface SomeInterface { String getValue() }

0 commit comments

Comments
 (0)