![JAR search and dependency download from the Maven repository](/logo.png)
com.apollographql.execution.processor.ApolloProcessor.kt Maven / Gradle / Ivy
package com.apollographql.execution.processor
import com.apollographql.execution.processor.codegen.CgFileBuilder
import com.apollographql.execution.processor.codegen.CoercingsBuilder
import com.apollographql.execution.processor.codegen.ExecutableSchemaBuilderBuilder
import com.apollographql.execution.processor.codegen.KotlinExecutableSchemaContext
import com.apollographql.execution.processor.codegen.SchemaDocumentBuilder
import com.apollographql.execution.processor.sir.SirClassName
import com.google.devtools.ksp.containingFile
import com.google.devtools.ksp.getConstructors
import com.google.devtools.ksp.isAbstract
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
class ApolloProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
private val packageName: String,
private val serviceName: String,
) : SymbolProcessor {
private var done = false
private fun getRootSymbol(resolver: Resolver, annotationName: String, dependencies: MutableList): KSClassDeclaration? {
val ret = getSymbolsWithAnnotation(resolver, annotationName, dependencies).toList()
if (ret.size > 1) {
val locations = ret.map { it.location }.joinToString("\n")
logger.error("There can be only one '$annotationName' annotated class, found ${ret.size}:\n$locations", ret.first())
return null
}
ret.forEach {
if (it !is KSClassDeclaration || it.isAbstract()) {
logger.error("'$annotationName' cannot be set on node $it", it)
return null
}
}
return ret.singleOrNull() as KSClassDeclaration?
}
private fun getSymbolsWithAnnotation(resolver: Resolver, annotationName: String, dependencies: MutableList): List {
return resolver.getSymbolsWithAnnotation(annotationName)
.filter { it.containingFile != null }
.toList()
.also {
dependencies.addAll(it.map { it.containingFile!! })
}
}
override fun process(resolver: Resolver): List {
if (done) {
return emptyList()
}
done = true
val ksFiles = mutableListOf()
val coercingDeclarations = getSymbolsWithAnnotation(resolver, "com.apollographql.execution.annotation.GraphQLCoercing", ksFiles)
val coercingDefinitions = getCoercingDefinitions(
logger,
coercingDeclarations
)
val scalarDeclarations = getSymbolsWithAnnotation(resolver, "com.apollographql.execution.annotation.GraphQLScalar", ksFiles)
val scalarDefinitions = getScalarDefinitions(
logger,
scalarDeclarations,
coercingDefinitions
)
val query = getRootSymbol(resolver, "com.apollographql.execution.annotation.GraphQLQueryRoot", ksFiles)
if (query == null) {
logger.error("No '@GraphqlQueryRoot' class found")
return emptyList()
}
val typeDefinitions = getTypeDefinitions(
logger,
scalarDefinitions,
query,
getRootSymbol(resolver, "com.apollographql.execution.annotation.GraphQLMutationRoot", ksFiles),
getRootSymbol(resolver, "com.apollographql.execution.annotation.GraphQLSubscriptionRoot", ksFiles)
)
val sirTypeDefinitions = scalarDefinitions + typeDefinitions
val context = KotlinExecutableSchemaContext(packageName)
val schemaDocumentBuilder = SchemaDocumentBuilder(
context = context,
serviceName = serviceName,
sirTypeDefinitions = sirTypeDefinitions
)
val builders = mutableListOf()
builders.add(schemaDocumentBuilder)
builders.add(
CoercingsBuilder(
context = context,
serviceName = serviceName,
sirTypeDefinitions = sirTypeDefinitions,
logger = logger
)
)
builders.add(
ExecutableSchemaBuilderBuilder(
context = context,
serviceName = serviceName,
schemaDocument = schemaDocumentBuilder.schemaDocument,
sirTypeDefinitions = sirTypeDefinitions
)
)
builders.forEach {
it.prepare()
}
val dependencies = Dependencies(true, *ksFiles.toTypedArray())
builders.map {
it.build()
.toBuilder()
.addFileComment(
"""
AUTO-GENERATED FILE. DO NOT MODIFY.
This class was automatically generated by Apollo GraphQL version '$VERSION'.
""".trimIndent()
).build()
}
.forEach { sourceFile ->
codeGenerator.createNewFile(
dependencies,
packageName = sourceFile.packageName,
// SourceFile contains .kt
fileName = sourceFile.name.substringBeforeLast('.'),
).bufferedWriter().use {
sourceFile.writeTo(it)
}
}
codeGenerator.createNewFileByPath(
dependencies,
"${serviceName}Schema.graphqls",
"",
).bufferedWriter().use {
it.write(schemaString(sirTypeDefinitions))
}
return emptyList()
}
}
internal fun KSClassDeclaration.hasNoArgsConstructor(): Boolean {
return getConstructors().any {
it.parameters.isEmpty()
}
}
internal fun KSAnnotated.deprecationReason(): String? {
return findAnnotation("Deprecated")?.getArgumentValueAsString("reason")
}
internal fun KSClassDeclaration.graphqlName(): String {
return graphqlNameOrNull() ?: simpleName.asString()
}
internal fun KSAnnotated.graphqlNameOrNull(): String? {
return findAnnotation("GraphQLName")?.getArgumentValueAsString("name")
}
internal fun KSPropertyDeclaration.graphqlName(): String {
return graphqlNameOrNull() ?: simpleName.asString()
}
internal fun KSAnnotated.findAnnotation(name: String): KSAnnotation? {
return annotations.firstOrNull { it.shortName.asString() == name }
}
internal fun KSAnnotation.getArgumentValue(name: String): Any? {
return arguments.firstOrNull {
it.name!!.asString() == name
}?.value
}
internal fun KSAnnotation.getArgumentValueAsString(name: String): String? {
return getArgumentValue(name)?.toString()
}
internal fun KSDeclaration.asClassName(): SirClassName {
return SirClassName(packageName.asString(), listOf(simpleName.asString()))
}
internal val executionContextClassName = SirClassName("com.apollographql.apollo.api", listOf("ExecutionContext"))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy