Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[apollo-execution] Allow to pass arguments to the root types #5352

Merged
merged 6 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,9 @@ public final class com/apollographql/apollo3/compiler/ir/IrTargetKt {
}

public final class com/apollographql/apollo3/compiler/ir/IrTargetObject {
public fun <init> (Ljava/lang/String;Lcom/apollographql/apollo3/compiler/ir/IrClassName;ZLjava/lang/String;Ljava/util/List;)V
public fun <init> (Ljava/lang/String;Lcom/apollographql/apollo3/compiler/ir/IrClassName;ZZLjava/lang/String;Ljava/util/List;)V
public final fun getFields ()Ljava/util/List;
public final fun getHasNoArgsConstructor ()Z
public final fun getName ()Ljava/lang/String;
public final fun getOperationType ()Ljava/lang/String;
public final fun getTargetClassName ()Lcom/apollographql/apollo3/compiler/ir/IrClassName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.apollographql.apollo3.compiler.codegen.kotlin

import com.apollographql.apollo3.compiler.capitalizeFirstLetter
import com.apollographql.apollo3.compiler.ir.IrTargetObject
import com.apollographql.apollo3.compiler.ir.asKotlinPoet
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.joinToCode

internal class ExecutableSchemaBuilderBuilder(
private val context: KotlinContext,
private val serviceName: String,
private val mainResolver: ClassName,
private val adapterRegistry: MemberName,
private val irTargetObjects: List<IrTargetObject>,
) : CgFileBuilder {
val simpleName = context.layout.capitalizedIdentifier("${serviceName}ExecutableSchemaBuilder")
override fun prepare() {

}

override fun build(): CgFile {
return CgFile(packageName = context.layout.executionPackageName(), fileName = simpleName, funSpecs = listOf(funSpec())
)
}

private fun funSpec(): FunSpec {
val rootIrTargetObjects = listOf("query", "mutation", "subscription").map { operationType ->
irTargetObjects.find { it.operationType == operationType }
}

return FunSpec.builder(simpleName)
.returns(KotlinSymbols.ExecutableSchemaBuilder)
.apply {
addParameter(ParameterSpec.builder("schema", KotlinSymbols.Schema).build())
rootIrTargetObjects.forEach { irTargetObject ->
if (irTargetObject != null) {
addParameter(
ParameterSpec.builder(
name = "root${irTargetObject.operationType?.capitalizeFirstLetter()}Object",
type = LambdaTypeName.get(parameters = emptyList(), returnType = irTargetObject.targetClassName.asKotlinPoet())
).apply {
if (irTargetObject.isSingleton) {
defaultValue(CodeBlock.of("{ %L }", irTargetObject.targetClassName.asKotlinPoet()))
} else if (irTargetObject.hasNoArgsConstructor) {
defaultValue(CodeBlock.of("{ %L() }", irTargetObject.targetClassName.asKotlinPoet()))
}
}.build())
}
}
}
.addCode(
CodeBlock.builder()
.add("return %L()\n", KotlinSymbols.ExecutableSchemaBuilder)
.add(".schema(schema)\n")
.add(".resolver(%L)\n", mainResolver)
.add(".adapterRegistry(%L)\n", adapterRegistry)
.add(".roots(%L.create(", KotlinSymbols.Roots)
.apply {
rootIrTargetObjects.map { irTargetObject ->
if (irTargetObject == null) {
CodeBlock.of("null")
} else {
CodeBlock.of("root${irTargetObject.operationType?.capitalizeFirstLetter()}Object")
}
}.joinToCode(", ")
.let {
add(it)
}
}
.add("))\n")
.build()
)
.build()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -389,18 +389,27 @@ internal object KotlinCodeGen {

val builders = mutableListOf<CgFileBuilder>()

builders.add(
MainResolverBuilder(
context = context,
serviceName = serviceName,
irTargetObjects = irTargetObjects
)
val mainResolverBuilder = MainResolverBuilder(
context = context,
serviceName = serviceName,
irTargetObjects = irTargetObjects
)
builders.add(mainResolverBuilder)

val adapterRegistryBuilder = AdapterRegistryBuilder(
context = context,
serviceName = serviceName,
codegenSchema = codegenSchema
)
builders.add(adapterRegistryBuilder)

builders.add(
AdapterRegistryBuilder(
ExecutableSchemaBuilderBuilder(
context = context,
serviceName = serviceName,
codegenSchema = codegenSchema
mainResolver = mainResolverBuilder.className,
adapterRegistry = adapterRegistryBuilder.memberName,
irTargetObjects = irTargetObjects
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
* Symbols can be [ClassName] or [MemberName]
*/
internal object KotlinSymbols {
val ExecutableSchemaBuilder = ClassName("com.apollographql.apollo3.execution", "ExecutableSchema", "Builder")
val Resolver = ClassName("com.apollographql.apollo3.execution", "Resolver")
val ResolveInfo = ClassName("com.apollographql.apollo3.execution", "ResolveInfo")
val Roots = ClassName("com.apollographql.apollo3.execution", "Roots")
val Schema = ClassName("com.apollographql.apollo3.ast", "Schema")
val ObjectType = ClassNames.ObjectType.toKotlinPoetClassName()
val ObjectTypeBuilder = ClassNames.ObjectTypeBuilder.toKotlinPoetClassName()
val InterfaceType = ClassNames.InterfaceType.toKotlinPoetClassName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ import com.apollographql.apollo3.compiler.ir.IrInputObjectType
import com.apollographql.apollo3.compiler.ir.IrNonNullType
import com.apollographql.apollo3.compiler.ir.IrScalarType
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.PropertySpec

internal class AdapterRegistryBuilder(
val context: KotlinContext,
val serviceName: String,
val codegenSchema: CodegenSchema
) : CgFileBuilder {
val packageName = context.layout.executionPackageName()
val simpleName = context.layout.capitalizedIdentifier("${serviceName}AdapterRegistry")
private val packageName = context.layout.executionPackageName()
private val simpleName = context.layout.capitalizedIdentifier("${serviceName}AdapterRegistry")

val memberName = MemberName(packageName, simpleName)

override fun prepare() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.apollographql.apollo3.compiler.codegen.kotlin.file

import com.apollographql.apollo3.compiler.applyIf
import com.apollographql.apollo3.compiler.capitalizeFirstLetter
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFile
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
Expand Down Expand Up @@ -30,8 +29,8 @@ internal class MainResolverBuilder(
val serviceName: String,
val irTargetObjects: List<IrTargetObject>,
) : CgFileBuilder {
val packageName = context.layout.executionPackageName()
val simpleName = context.layout.capitalizedIdentifier("${serviceName}Resolver")
private val packageName = context.layout.executionPackageName()
private val simpleName = context.layout.capitalizedIdentifier("${serviceName}Resolver")

val className = ClassName(packageName, simpleName)

Expand All @@ -45,47 +44,15 @@ internal class MainResolverBuilder(
)
}

private fun primaryConstructor(): FunSpec {
return FunSpec.constructorBuilder()
.build()
}

private fun typeSpec(): TypeSpec {
return TypeSpec.classBuilder(simpleName)
.primaryConstructor(primaryConstructor())
return TypeSpec.objectBuilder(simpleName)
.addSuperinterface(ClassName("com.apollographql.apollo3.execution", "MainResolver"))
.addProperty(typenamesPropertySpec())
.addProperty(resolversPropertySpec())
.addFunction(typenameFunSpec())
.addFunction(resolveFunSpec())
.addAnnotation(suppressDeprecationAnnotationSpec)
.apply {
listOf("query", "mutation", "subscription").forEach { operationType ->
val rootTargetObject = irTargetObjects.firstOrNull { it.operationType == operationType }

addFunction(rootObjectFunSpec(operationType, rootTargetObject))
}
}
.build()
}

private fun rootObjectFunSpec(operationType: String, irTargetObject: IrTargetObject?): FunSpec {

return FunSpec.builder("root${operationType.capitalizeFirstLetter()}Object")
.addModifiers(KModifier.OVERRIDE)
.returns(KotlinSymbols.Any.copy(nullable = true))
.apply {
if (irTargetObject == null) {
addCode("return null")
} else {
val maybeParenthesis = if (irTargetObject.isSingleton) {
""
} else {
"()"
}
addCode("return %T$maybeParenthesis", irTargetObject.targetClassName.asKotlinPoet())
}
}
.build()
}

Expand Down Expand Up @@ -232,7 +199,7 @@ internal class MainResolverBuilder(
builder.add(
"%L·=·it.getArgument<%T>(%S)",
irTargetArgument.targetName,
context.resolver.resolveIrType(type, false, false),
context.resolver.resolveIrType(type = type, jsExport = false, isInterface = false),
irTargetArgument.name,
)
if (irTargetArgument.type !is IrOptionalType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class IrTargetObject(
val name: String,
val targetClassName: IrClassName,
val isSingleton: Boolean,
val hasNoArgsConstructor: Boolean,
val operationType: String?,
val fields: List<IrTargetField>,
)
15 changes: 11 additions & 4 deletions libraries/apollo-debug-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ kotlin {
dependencies {
implementation(project(":apollo-normalized-cache"))

// apollo-execution is not published: we bundle it into the aar artifact
compileOnly(project(":apollo-execution"))
api(project(":apollo-ast"))
api(project(":apollo-api"))
}
}

Expand All @@ -44,13 +43,21 @@ val shadow = configurations.create("shadow") {

dependencies {
add("kspCommonMainMetadata", project(":apollo-ksp"))
add("kspCommonMainMetadata", apollo.apolloKspProcessor(file("src/androidMain/resources/schema.graphqls"), "apolloDebugServer", "com.apollographql.apollo3.debugserver.internal.graphql"))
add(
"kspCommonMainMetadata",
apollo.apolloKspProcessor(
schema = file(path = "src/androidMain/resources/schema.graphqls"),
service = "apolloDebugServer",
packageName = "com.apollographql.apollo3.debugserver.internal.graphql"
)
)
// apollo-execution is not published: we bundle it into the aar artifact
add(shadow.name, project(":apollo-execution")) {
isTransitive = false
}
}

configurations.getByName("compileOnly").extendsFrom(shadow)
configurations.getByName(kotlin.sourceSets.getByName("commonMain").compileOnlyConfigurationName).extendsFrom(shadow)

android {
compileSdk = libs.versions.android.sdkversion.compile.get().toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@ package com.apollographql.apollo3.debugserver
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.debugserver.internal.server.Server
import com.apollographql.apollo3.debugserver.internal.server.createServer
import okio.withLock
import java.util.concurrent.locks.ReentrantLock

object ApolloDebugServer {
private val apolloClients = mutableMapOf<ApolloClient, String>()
private var server: Server? = null
private val lock = ReentrantLock()

fun registerApolloClient(apolloClient: ApolloClient, id: String = "client") {
if (apolloClients.containsKey(apolloClient)) error("Client '$apolloClient' already registered")
if (apolloClients.containsValue(id)) error("Name '$id' already registered")
apolloClients[apolloClient] = id
startOrStopServer()
lock.withLock {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that works? This locks the writer but readers from the HTTP request thread won't use the lock and might iterate the list while it is modified

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you're right! I liked the AtomicReference idea but didn't like that it's JVM only. But maybe that's ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AtomicFu has KMP versions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah great! 🎉
I think I'll use JVM AtomicReference to avoid adding the AtomicFu dependency, but with the knowledge that we can use it to support more targets.

if (apolloClients.containsKey(apolloClient)) error("Client '$apolloClient' already registered")
if (apolloClients.containsValue(id)) error("Name '$id' already registered")
apolloClients[apolloClient] = id
startOrStopServer()
}
}

fun unregisterApolloClient(apolloClient: ApolloClient) {
apolloClients.remove(apolloClient)
startOrStopServer()
lock.withLock {
apolloClients.remove(apolloClient)
startOrStopServer()
}
}

private fun startOrStopServer() {
if (apolloClients.isEmpty()) {
server?.stop()
server = null
} else {
if (server == null) {
server = createServer(apolloClients).apply {
Expand Down
Loading
Loading