graphql.nadel.schema.OverallSchemaGenerator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nadel Show documentation
Show all versions of nadel Show documentation
Nadel is a Java library that combines multiple GrahpQL services together into one API.
package graphql.nadel.schema
import graphql.GraphQLException
import graphql.language.FieldDefinition
import graphql.language.ObjectTypeDefinition
import graphql.language.ObjectTypeDefinition.newObjectTypeDefinition
import graphql.language.ScalarTypeDefinition.newScalarTypeDefinition
import graphql.language.SchemaDefinition
import graphql.language.SourceLocation
import graphql.nadel.NadelDefinitionRegistry
import graphql.nadel.NadelOperationKind
import graphql.nadel.util.AnyNamedNode
import graphql.nadel.util.AnySDLDefinition
import graphql.nadel.util.AnySDLNamedDefinition
import graphql.nadel.util.isExtensionDef
import graphql.scalars.ExtendedScalars
import graphql.schema.GraphQLSchema
import graphql.schema.idl.RuntimeWiring
import graphql.schema.idl.SchemaGenerator
import graphql.schema.idl.TypeDefinitionRegistry
import graphql.schema.idl.WiringFactory
internal class OverallSchemaGenerator {
fun buildOverallSchema(
serviceRegistries: List,
wiringFactory: WiringFactory,
): GraphQLSchema {
val schemaGenerator = SchemaGenerator()
val runtimeWiring = RuntimeWiring.newRuntimeWiring()
.wiringFactory(wiringFactory)
.build()
return schemaGenerator.makeExecutableSchema(createTypeRegistry(serviceRegistries), runtimeWiring)
}
private fun createTypeRegistry(serviceRegistries: List): TypeDefinitionRegistry {
val topLevelFields = NadelOperationKind.entries
.associateWith {
mutableListOf()
}
val overallRegistry = TypeDefinitionRegistry()
val allDefinitions = ArrayList()
for (definitionRegistry in serviceRegistries) {
collectTypes(topLevelFields, allDefinitions, definitionRegistry)
}
// Create merged operation types in schema for all top level fields
topLevelFields.keys.forEach { operationKind ->
val fields = topLevelFields[operationKind]
if (fields?.isNotEmpty() == true) {
overallRegistry.add(
newObjectTypeDefinition()
.name(operationKind.defaultTypeName)
.sourceLocation(SourceLocation(-1, -1, "Generated"))
.fieldDefinitions(fields)
.build()
)
}
}
// add our custom directives if they are not present
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationArgumentDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedDirectiveDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.renamedDirectiveDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hiddenDirectiveDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationFromArgumentDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelBatchObjectIdentifiedByDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationTemplateEnumDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedFromDirectiveDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedTemplateDirectiveDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultFieldPredicateDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultConditionDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationConditionDefinition)
addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.deferDirectiveDefinition)
addIfNotPresent(
overallRegistry, allDefinitions, newScalarTypeDefinition()
.name(ExtendedScalars.Json.name)
.build()
)
for (definition in allDefinitions) {
val error = overallRegistry.add(definition)
if (error.isPresent) {
throw GraphQLException("Unable to add definition to overall registry: " + error.get().message)
}
}
return overallRegistry
}
private inline fun addIfNotPresent(
overallRegistry: TypeDefinitionRegistry,
allDefinitions: List,
namedDefinition: T,
) {
if (!containsElement(allDefinitions, namedDefinition)) {
overallRegistry.add(namedDefinition)
}
}
private inline fun containsElement(
allDefinitions: List,
def: T,
): Boolean {
return allDefinitions
.asSequence()
.filterIsInstance()
.filter { it.name == def.name }
// if it's an `extent type Foo` then it does not count since we need an actual `type Foo` defined
.filterNot { it.isExtensionDef }
.any { element ->
require(element is T) {
val name = def.name
val expected = T::class.java.name
val actual = element.javaClass.name
"The element schema $name is expected to be a $expected but is in fact a $actual"
}
element.name == def.name
}
}
private fun collectTypes(
topLevelFields: Map>,
allDefinitions: MutableList,
definitionRegistry: NadelDefinitionRegistry,
) {
val opDefinitions = definitionRegistry.operationMap
val opTypeNames: MutableSet = HashSet(3)
opDefinitions.keys.forEach { opType: NadelOperationKind ->
val opsDefinitions = opDefinitions[opType]
if (opsDefinitions != null) {
// Collect field definitions
for (objectTypeDefinition in opsDefinitions) {
topLevelFields[opType]!!.addAll(objectTypeDefinition.fieldDefinitions)
}
// Record down the type name for each operation
val operationTypeName = definitionRegistry.getOperationTypeName(opType)
opTypeNames.add(operationTypeName)
}
}
definitionRegistry
.definitions
.asSequence()
.filter { definition ->
if (definition is ObjectTypeDefinition) {
// Don't add operation types
!opTypeNames.contains(definition.name)
} else {
true
}
}
.filter { definition ->
// Don't add schema definitions, everything gets merged into a generated type with the default name
definition !is SchemaDefinition
}
.forEach(allDefinitions::add)
}
}