All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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