@@ -35,15 +35,25 @@ internal class SchemaClassScanner(
3535 private val resolverInfos = resolvers.asSequence().minus(queryResolvers).minus(mutationResolvers).minus(subscriptionResolvers).map { NormalResolverInfo (it, options) }.toList()
3636
3737 private val initialDictionary = initialDictionary.mapValues { InitialDictionaryEntry (it.value) }
38- private val extensionDefinitions = allDefinitions.filterIsInstance<ObjectTypeExtensionDefinition >()
39- private val inputExtensionDefinitions = allDefinitions.filterIsInstance<InputObjectTypeExtensionDefinition >()
4038 private val directiveDefinitions = allDefinitions.filterIsInstance<DirectiveDefinition >()
4139 private val scalarDefinitions = allDefinitions.filterIsInstance<ScalarTypeDefinition >()
4240
43- private val definitionsByName = (allDefinitions.filterIsInstance<TypeDefinition <* >>() - extensionDefinitions - inputExtensionDefinitions).associateBy { it.name }
44- private val objectDefinitions = (allDefinitions.filterIsInstance<ObjectTypeDefinition >() - extensionDefinitions)
41+ private val typeDefinitions = allDefinitions.filterIsInstance<TypeDefinition <* >>()
42+ private val baseTypeNames = typeDefinitions.filter { it !is SDLExtensionDefinition }.map { it.name }.toSet()
43+
44+ // the first extension of an undefined union, enum, interface or scalar stands in for its definition
45+ private val loneExtensions = typeDefinitions
46+ .filter { it is SDLExtensionDefinition && it !is ObjectTypeDefinition && it !is InputObjectTypeDefinition && it.name !in baseTypeNames }
47+ .distinctBy { it.name }
48+
49+ private val definitionsByName = typeDefinitions.filter { it !is SDLExtensionDefinition || it in loneExtensions }.associateBy { it.name }
50+ // without extensions of a different kind than their type, which are ignored with a warning, see init
51+ private val typeExtensions: TypeExtensions = typeDefinitions
52+ .filter { it is SDLExtensionDefinition && it !in loneExtensions && ! extendsOtherKind(it) }
53+ .groupBy { it.name }
54+ private val objectDefinitions = allDefinitions.filterIsInstance<ObjectTypeDefinition >().filter { it !is ObjectTypeExtensionDefinition }
4555 private val objectDefinitionsByName = objectDefinitions.associateBy { it.name }
46- private val interfaceDefinitionsByName = allDefinitions .filterIsInstance<InterfaceTypeDefinition >().associateBy { it.name }
56+ private val interfaceDefinitionsByName = definitionsByName.values .filterIsInstance<InterfaceTypeDefinition >().associateBy { it.name }
4757
4858 private val fieldResolverScanner = FieldResolverScanner (options)
4959 private val typeClassMatcher = TypeClassMatcher (definitionsByName)
@@ -61,6 +71,11 @@ internal class SchemaClassScanner(
6171 }
6272 }
6373
74+ typeDefinitions.filter { extendsOtherKind(it) }.forEach { extension ->
75+ val definition = definitionsByName.getValue(extension.name)
76+ log.warn(" Type '${extension.name} ' is defined as ${definition.kind()} , but extended as ${extension.kind()} . The extension is ignored." )
77+ }
78+
6479 // Input object types never go into the type => class bimap that interface and union type resolution use, so several of
6580 // them can share a class, e.g. ObjectNode
6681 initialDictionary
@@ -216,9 +231,8 @@ internal class SchemaClassScanner(
216231 * The names of the types used by the fields, arguments and input fields of [definitions], including fields added by extensions.
217232 */
218233 private fun typeNamesUsedByFields (definitions : Set <TypeDefinition <* >>): Set <String > {
219- val names = definitions.map { it.name }.toSet()
220- return (definitions + extensionDefinitions + inputExtensionDefinitions)
221- .filter { it.name in names }
234+ return definitions
235+ .flatMap { it.withExtensions(typeExtensions) }
222236 .flatMap { definition ->
223237 when (definition) {
224238 is ImplementingTypeDefinition <* > -> definition.fieldDefinitions.flatMap { field ->
@@ -278,6 +292,7 @@ internal class SchemaClassScanner(
278292 .description(getDocumentation(definition, options) ? : provided.description)
279293 .coercing(provided.coercing)
280294 .definition(definition)
295+ .extensionDefinitions(typeExtensions.extensionsOf<ScalarTypeExtensionDefinition >(definition.name))
281296 .build()
282297 }.associateBy { it.name }
283298
@@ -300,15 +315,15 @@ internal class SchemaClassScanner(
300315 validateRootResolversWereUsed(rootTypeHolder.mutation, fieldResolvers)
301316 validateRootResolversWereUsed(rootTypeHolder.subscription, fieldResolvers)
302317
303- val definitions = observedDefinitions + extensionDefinitions + inputExtensionDefinitions + directiveDefinitions
318+ val definitions = observedDefinitions + directiveDefinitions
304319
305320 // The enums in unvalidatedTypes come from directives without a class, so they can only be built when no field uses them.
306321 // A field hands the enum's values to user code even when the scanner found no class through it, e.g. an input field bound
307322 // to a Map or an unimplemented field's argument.
308323 val typeNamesUsedByFields = typeNamesUsedByFields(observedDefinitions)
309324 val directiveOnlyEnums = unvalidatedTypes.filterIsInstance<EnumTypeDefinition >().filter { it.name !in typeNamesUsedByFields }.toSet()
310325
311- return ScannedSchemaObjects (dictionary, definitions, scalars, rootInfo, fieldResolversByType.toMap(), unusedDefinitions, directiveOnlyEnums)
326+ return ScannedSchemaObjects (dictionary, definitions, typeExtensions, scalars, rootInfo, fieldResolversByType.toMap(), unusedDefinitions, directiveOnlyEnums)
312327 }
313328
314329 private fun validateRootResolversWereUsed (rootType : RootType ? , fieldResolvers : List <FieldResolver >) {
@@ -326,16 +341,20 @@ internal class SchemaClassScanner(
326341
327342 private fun getAllObjectTypesImplementingDiscoveredInterfaces (): List <ObjectTypeDefinition > {
328343 return dictionary.keys.filterIsInstance<InterfaceTypeDefinition >().map { iface ->
329- objectDefinitions.filter { obj -> obj.implements.filterIsInstance<TypeName >().any { it.name == iface.name } }
344+ objectDefinitions.filter { obj ->
345+ obj.withExtensions(typeExtensions).flatMap { it.implements }.filterIsInstance<TypeName >().any { it.name == iface.name }
346+ }
330347 }.flatten().distinctBy { it.name }
331348 }
332349
333350 private fun getAllObjectTypeMembersOfDiscoveredUnions (): List <ObjectTypeDefinition > {
334351 val unionTypeNames = dictionary.keys.filterIsInstance<UnionTypeDefinition >().map { union -> union.name }.toSet()
335352 return dictionary.keys.filterIsInstance<UnionTypeDefinition >().map { union ->
336- union.memberTypes.filterIsInstance<TypeName >().filter { ! unionTypeNames.contains(it.name) }.map {
337- objectDefinitionsByName[it.name]
338- ? : throw SchemaClassScannerError (" No object type found with name '${it.name} ' for union: $union " )
353+ union.withExtensions(typeExtensions).flatMap { definition ->
354+ definition.memberTypes.filterIsInstance<TypeName >().filter { ! unionTypeNames.contains(it.name) }.map {
355+ objectDefinitionsByName[it.name]
356+ ? : throw SchemaClassScannerError (" No object type found with name '${it.name} ' for union: $definition " )
357+ }
339358 }
340359 }.flatten().distinct()
341360 }
@@ -346,7 +365,7 @@ internal class SchemaClassScanner(
346365 */
347366 private fun handleInterfaceFieldTypes (): Boolean {
348367 val newTypes = dictionary.keys.filterIsInstance<InterfaceTypeDefinition >().flatMap { iface ->
349- iface.fieldDefinitions.mapNotNull { field ->
368+ iface.withExtensions(typeExtensions).flatMap { it. fieldDefinitions } .mapNotNull { field ->
350369 val type = definitionsByName[(field.type.unwrap() as TypeName ).name]
351370 if ((type is UnionTypeDefinition || type is InterfaceTypeDefinition ) && ! dictionary.containsKey(type)) {
352371 type to FieldTypeReference (" ${iface.name} .${field.name} " )
@@ -429,8 +448,8 @@ internal class SchemaClassScanner(
429448 dictionary
430449 .filterKeys { definition ->
431450 when (definition) {
432- is UnionTypeDefinition -> definition.memberTypes.filterIsInstance<TypeName >().any { it.name == type.name }
433- is InterfaceTypeDefinition -> type.implements.filterIsInstance<TypeName >().any { it.name == definition.name }
451+ is UnionTypeDefinition -> definition.withExtensions(typeExtensions).flatMap { it. memberTypes } .filterIsInstance<TypeName >().any { it.name == type.name }
452+ is InterfaceTypeDefinition -> type.withExtensions(typeExtensions).flatMap { it. implements } .filterIsInstance<TypeName >().any { it.name == definition.name }
434453 else -> false
435454 }
436455 }
@@ -503,7 +522,7 @@ internal class SchemaClassScanner(
503522 resolverInfo.dataClassType != Any ::class .java && resolverInfo.dataClassType.isAssignableFrom(dataClass)
504523
505524 private fun scanResolverInfoForPotentialMatches (type : ObjectTypeDefinition , resolverInfo : ResolverInfo ) {
506- type.getExtendedFieldDefinitions(extensionDefinitions) .forEach { field ->
525+ type.withExtensions(typeExtensions).flatMap { it.fieldDefinitions } .forEach { field ->
507526 val fieldResolver = fieldResolverScanner.findFieldResolver(field, resolverInfo)
508527
509528 fieldResolversByType.getOrPut(type) { mutableMapOf () }[fieldResolver.field] = fieldResolver
@@ -573,8 +592,7 @@ internal class SchemaClassScanner(
573592 */
574593 private fun handleInputObjectFields (graphQLType : InputObjectTypeDefinition , javaType : JavaType ? ) {
575594 val clazz = javaType?.unwrap()
576- val inputObjectTypes = listOf (graphQLType) + inputExtensionDefinitions.filter { it.name == graphQLType.name }
577- inputObjectTypes
595+ graphQLType.withExtensions(typeExtensions)
578596 .flatMap { it.inputValueDefinitions }
579597 .forEach { inputValueDefinition ->
580598 val inputGraphQLType = inputValueDefinition.type.unwrap()
@@ -645,14 +663,30 @@ internal class SchemaClassScanner(
645663 }
646664
647665 private fun scanInterfacesOfType (graphQLType : ObjectTypeDefinition ) {
648- graphQLType.implements.forEach {
666+ graphQLType.withExtensions(typeExtensions).flatMap { it. implements } .forEach {
649667 if (it is TypeName ) {
650668 handleFoundType(interfaceDefinitionsByName[it.name]
651669 ? : throw SchemaClassScannerError (" Object type ${graphQLType.name} declared interface ${it.name} , but no interface with that name was found in the schema!" ), null , InterfaceReference (graphQLType))
652670 }
653671 }
654672 }
655673
674+ // e.g. `extend union Foo` of `type Foo`
675+ private fun extendsOtherKind (definition : TypeDefinition <* >): Boolean {
676+ val type = definitionsByName[definition.name]
677+ return definition is SDLExtensionDefinition && type != null && type.kind() != definition.kind()
678+ }
679+
680+ private fun TypeDefinition <* >.kind (): String = when (this ) {
681+ is ObjectTypeDefinition -> " an object type"
682+ is InterfaceTypeDefinition -> " an interface"
683+ is UnionTypeDefinition -> " a union"
684+ is EnumTypeDefinition -> " an enum"
685+ is InputObjectTypeDefinition -> " an input object type"
686+ is ScalarTypeDefinition -> " a scalar"
687+ else -> javaClass.simpleName
688+ }
689+
656690 private fun enqueue (graphQLType : ObjectTypeDefinition , javaType : JavaType ) {
657691 queue.add(QueueItem (graphQLType, javaType))
658692 }
0 commit comments