Skip to content

Commit 7996b45

Browse files
feat: Add option to allow unused types
1 parent d780322 commit 7996b45

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ internal class SchemaClassScanner(
8484
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." }
8585
} while (scanQueue())
8686

87+
if (options.includeUnusedTypes) {
88+
do {
89+
val unusedDefinitions = (definitionsByName.values - (dictionary.keys.toSet() + unvalidatedTypes))
90+
.filter { definition -> definition.name != "PageInfo" }
91+
.filterIsInstance<ObjectTypeDefinition>().distinct()
92+
93+
if (unusedDefinitions.isEmpty()) break
94+
95+
val unusedDefinition = unusedDefinitions.first()
96+
97+
handleInterfaceOrUnionSubTypes(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." }
98+
} while (scanQueue())
99+
}
100+
87101
return validateAndCreateResult(rootTypeHolder)
88102
}
89103

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

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)