diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 2aa1ef4e481..1bde7f7d451 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -23,6 +23,9 @@ + + diff --git a/libraries/apollo-compiler/api/apollo-compiler.api b/libraries/apollo-compiler/api/apollo-compiler.api index 12ca5b4bb97..9d3a26b2a88 100644 --- a/libraries/apollo-compiler/api/apollo-compiler.api +++ b/libraries/apollo-compiler/api/apollo-compiler.api @@ -666,8 +666,9 @@ public final class com/apollographql/apollo3/compiler/ir/IrTargetKt { } public final class com/apollographql/apollo3/compiler/ir/IrTargetObject { - public fun (Ljava/lang/String;Lcom/apollographql/apollo3/compiler/ir/IrClassName;ZLjava/lang/String;Ljava/util/List;)V + public fun (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; diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/ExecutableSchemaBuilderBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/ExecutableSchemaBuilderBuilder.kt new file mode 100644 index 00000000000..a67f12294ce --- /dev/null +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/ExecutableSchemaBuilderBuilder.kt @@ -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, +) : 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() + } + +} diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinCodeGen.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinCodeGen.kt index ec9055dd223..05902ae6784 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinCodeGen.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinCodeGen.kt @@ -389,18 +389,27 @@ internal object KotlinCodeGen { val builders = mutableListOf() - 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 ) ) diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinSymbols.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinSymbols.kt index 64e7799e7f4..d9c8409fbfe 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinSymbols.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/KotlinSymbols.kt @@ -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() diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/AdapterRegistryBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/AdapterRegistryBuilder.kt index f16dc01eee5..6a0bdb44e78 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/AdapterRegistryBuilder.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/AdapterRegistryBuilder.kt @@ -16,6 +16,7 @@ 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( @@ -23,8 +24,10 @@ internal class AdapterRegistryBuilder( 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() { } diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/MainResolverBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/MainResolverBuilder.kt index 24eaec17a25..ff4e9cf7778 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/MainResolverBuilder.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/codegen/kotlin/file/MainResolverBuilder.kt @@ -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 @@ -30,8 +29,8 @@ internal class MainResolverBuilder( val serviceName: String, val irTargetObjects: List, ) : 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) @@ -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() } @@ -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) { diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ir/IrTarget.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ir/IrTarget.kt index 66c983588d3..e84a88f47ba 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ir/IrTarget.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ir/IrTarget.kt @@ -33,6 +33,7 @@ class IrTargetObject( val name: String, val targetClassName: IrClassName, val isSingleton: Boolean, + val hasNoArgsConstructor: Boolean, val operationType: String?, val fields: List, ) \ No newline at end of file diff --git a/libraries/apollo-debug-server/build.gradle.kts b/libraries/apollo-debug-server/build.gradle.kts index c87556ca83f..e0d3f598002 100644 --- a/libraries/apollo-debug-server/build.gradle.kts +++ b/libraries/apollo-debug-server/build.gradle.kts @@ -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")) } } @@ -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() diff --git a/libraries/apollo-debug-server/src/androidMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.android.kt b/libraries/apollo-debug-server/src/androidMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.android.kt index 09c4c8d3d4e..23cd85f2c89 100644 --- a/libraries/apollo-debug-server/src/androidMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.android.kt +++ b/libraries/apollo-debug-server/src/androidMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.android.kt @@ -14,13 +14,14 @@ import kotlinx.coroutines.launch import java.io.BufferedReader import java.io.PrintStream import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicReference internal actual fun createServer( - apolloClients: Map, + apolloClients: AtomicReference>, ): Server = AndroidServer(apolloClients) private class AndroidServer( - apolloClients: Map, + apolloClients: AtomicReference>, ) : Server { companion object { private const val SOCKET_NAME_PREFIX = "apollo_debug_" diff --git a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/ApolloDebugServer.kt b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/ApolloDebugServer.kt index 036ef53e91d..deb40167a67 100644 --- a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/ApolloDebugServer.kt +++ b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/ApolloDebugServer.kt @@ -3,26 +3,28 @@ 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 java.util.concurrent.atomic.AtomicReference object ApolloDebugServer { - private val apolloClients = mutableMapOf() + private val apolloClients = AtomicReference>(emptyMap()) private var server: Server? = null 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 + if (apolloClients.get().containsKey(apolloClient)) error("Client '$apolloClient' already registered") + if (apolloClients.get().containsValue(id)) error("Name '$id' already registered") + apolloClients.set(apolloClients.get() + (apolloClient to id)) startOrStopServer() } fun unregisterApolloClient(apolloClient: ApolloClient) { - apolloClients.remove(apolloClient) + apolloClients.set(apolloClients.get() - apolloClient) startOrStopServer() } private fun startOrStopServer() { - if (apolloClients.isEmpty()) { + if (apolloClients.get().isEmpty()) { server?.stop() + server = null } else { if (server == null) { server = createServer(apolloClients).apply { diff --git a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/graphql/GraphQL.kt b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/graphql/GraphQL.kt index bfa7f5a6313..add486149e3 100644 --- a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/graphql/GraphQL.kt +++ b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/graphql/GraphQL.kt @@ -15,31 +15,29 @@ import com.apollographql.apollo3.ast.toSchema import com.apollographql.apollo3.cache.normalized.api.CacheKey import com.apollographql.apollo3.cache.normalized.api.Record import com.apollographql.apollo3.cache.normalized.apolloStore -import com.apollographql.apollo3.debugserver.internal.graphql.execution.ApolloDebugServerAdapterRegistry -import com.apollographql.apollo3.debugserver.internal.graphql.execution.ApolloDebugServerResolver +import com.apollographql.apollo3.debugserver.internal.graphql.execution.ApolloDebugServerExecutableSchemaBuilder import com.apollographql.apollo3.execution.ExecutableSchema import com.apollographql.apollo3.execution.GraphQLRequest import com.apollographql.apollo3.execution.GraphQLRequestError import com.apollographql.apollo3.execution.parsePostGraphQLRequest import kotlinx.coroutines.runBlocking import okio.Buffer +import java.util.concurrent.atomic.AtomicReference import kotlin.reflect.KClass internal expect fun getExecutableSchema(): String internal class GraphQL( - private val apolloClients: Map, + private val apolloClients: AtomicReference>, ) { private val executableSchema: ExecutableSchema by lazy { val schema = getExecutableSchema() .toGQLDocument() .toSchema() - ExecutableSchema.Builder() - .schema(schema) - .resolver(ApolloDebugServerResolver()) - .adapterRegistry(ApolloDebugServerAdapterRegistry) - .build() + ApolloDebugServerExecutableSchemaBuilder(schema) { + Query(apolloClients) + }.build() } fun executeGraphQL(jsonBody: String): String { @@ -48,52 +46,31 @@ internal class GraphQL( return graphQLRequestResult.message } graphQLRequestResult as GraphQLRequest - val dumps = apolloClients.mapValues { (apolloClient, _) -> - runBlocking { apolloClient.apolloStore.dump() } - } - val apolloDebugContext = ApolloDebugContext(apolloClients, dumps) - val graphQlResponse = executableSchema.execute(graphQLRequestResult, apolloDebugContext) + + val graphQlResponse = executableSchema.execute(graphQLRequestResult, ExecutionContext.Empty) + val buffer = Buffer() graphQlResponse.serialize(buffer) return buffer.readUtf8() } - - internal class ApolloDebugContext( - val apolloClients: Map, - val dumps: Map, Map>>, - ) : ExecutionContext.Element { - override val key: ExecutionContext.Key = Key - - companion object Key : ExecutionContext.Key - } } @ApolloObject -internal class Query { - private fun graphQLApolloClients(apolloDebugContext: GraphQL.ApolloDebugContext) = - apolloDebugContext.apolloClients.map { (apolloClient, apolloClientId) -> +internal class Query(private val apolloClients: AtomicReference>) { + private fun graphQLApolloClients() = + apolloClients.get().map { (apolloClient, apolloClientId) -> GraphQLApolloClient( id = apolloClientId, - displayName = apolloClientId, - normalizedCaches = apolloDebugContext.dumps[apolloClient]!!.keys.map { clazz -> - NormalizedCache( - id = "$apolloClientId:${clazz.normalizedCacheName()}", - displayName = clazz.normalizedCacheName(), - recordCount = apolloDebugContext.dumps[apolloClient]!![clazz]!!.size, - records = apolloDebugContext.dumps[apolloClient]!![clazz]!!.values.map { record -> GraphQLRecord(record) } - ) - } + apolloClient = apolloClient ) } - fun apolloClients(executionContext: ExecutionContext): List { - val apolloDebugContext = executionContext[GraphQL.ApolloDebugContext]!! - return graphQLApolloClients(apolloDebugContext) + fun apolloClients(): List { + return graphQLApolloClients() } - fun apolloClient(executionContext: ExecutionContext, id: String): GraphQLApolloClient? { - val apolloDebugContext = executionContext[GraphQL.ApolloDebugContext]!! - return graphQLApolloClients(apolloDebugContext).firstOrNull { it.id() == id } + fun apolloClient(id: String): GraphQLApolloClient? { + return graphQLApolloClients().firstOrNull { it.id() == id } } } @@ -101,34 +78,35 @@ internal class Query { @GraphQLName("ApolloClient") internal class GraphQLApolloClient( private val id: String, - private val displayName: String, - private val normalizedCaches: List, + private val apolloClient: ApolloClient, ) { fun id() = id - fun displayName() = displayName + fun displayName() = id - fun normalizedCaches(): List = normalizedCaches + fun normalizedCaches(): List = runBlocking { apolloClient.apolloStore.dump() }.map { + NormalizedCache(id, it.key, it.value) + } fun normalizedCache(id: String): NormalizedCache? { - return normalizedCaches.firstOrNull { it.id() == id } + return normalizedCaches().firstOrNull { it.id() == id } } } @ApolloObject internal class NormalizedCache( - private val id: String, - private val displayName: String, - private val recordCount: Int, - private val records: List, + apolloClientId: String, + private val clazz: KClass<*>, + private val records: Map, ) { + private val id: String = "$apolloClientId:${clazz.normalizedCacheName()}" fun id() = id - fun displayName() = displayName + fun displayName() = clazz.normalizedCacheName() - fun recordCount() = recordCount + fun recordCount() = records.count() - fun records(): List = records + fun records(): List = records.map { GraphQLRecord(it.value) } } @ApolloObject diff --git a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.kt b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.kt index 2bef82a725d..92a8715d21d 100644 --- a/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.kt +++ b/libraries/apollo-debug-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.kt @@ -1,6 +1,7 @@ package com.apollographql.apollo3.debugserver.internal.server import com.apollographql.apollo3.ApolloClient +import java.util.concurrent.atomic.AtomicReference internal interface Server { fun start() @@ -8,7 +9,7 @@ internal interface Server { } internal expect fun createServer( - apolloClients: Map, + apolloClients: AtomicReference>, ): Server internal class NoOpServer : Server { diff --git a/libraries/apollo-debug-server/src/jvmMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.jvm.kt b/libraries/apollo-debug-server/src/jvmMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.jvm.kt index cacf3d9968f..a668b4b08d8 100644 --- a/libraries/apollo-debug-server/src/jvmMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.jvm.kt +++ b/libraries/apollo-debug-server/src/jvmMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.jvm.kt @@ -1,5 +1,6 @@ package com.apollographql.apollo3.debugserver.internal.server import com.apollographql.apollo3.ApolloClient +import java.util.concurrent.atomic.AtomicReference -internal actual fun createServer(apolloClients: Map): Server = NoOpServer() +internal actual fun createServer(apolloClients: AtomicReference>): Server = NoOpServer() diff --git a/libraries/apollo-execution/api/apollo-execution.api b/libraries/apollo-execution/api/apollo-execution.api index 17b3b7c5506..1c90327873b 100644 --- a/libraries/apollo-execution/api/apollo-execution.api +++ b/libraries/apollo-execution/api/apollo-execution.api @@ -8,20 +8,11 @@ public final class com/apollographql/apollo3/execution/ExecutableSchema$Builder public final fun adapterRegistry (Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; public final fun addInstrumentation (Lcom/apollographql/apollo3/execution/Instrumentation;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; public final fun build ()Lcom/apollographql/apollo3/execution/ExecutableSchema; - public final fun getAdapterRegistry ()Lcom/apollographql/apollo3/api/CustomScalarAdapters; - public final fun getInstrumentations ()Ljava/util/List; - public final fun getPersistedDocumentCache ()Lcom/apollographql/apollo3/execution/PersistedDocumentCache; - public final fun getResolver ()Lcom/apollographql/apollo3/execution/MainResolver; - public final fun getSchema ()Lcom/apollographql/apollo3/ast/Schema; public final fun persistedDocumentCache (Lcom/apollographql/apollo3/execution/PersistedDocumentCache;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; public final fun resolver (Lcom/apollographql/apollo3/execution/MainResolver;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; + public final fun roots (Lcom/apollographql/apollo3/execution/Roots;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; public final fun schema (Lcom/apollographql/apollo3/ast/Schema;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; public final fun schema (Ljava/lang/String;)Lcom/apollographql/apollo3/execution/ExecutableSchema$Builder; - public final fun setAdapterRegistry (Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V - public final fun setInstrumentations (Ljava/util/List;)V - public final fun setPersistedDocumentCache (Lcom/apollographql/apollo3/execution/PersistedDocumentCache;)V - public final fun setResolver (Lcom/apollographql/apollo3/execution/MainResolver;)V - public final fun setSchema (Lcom/apollographql/apollo3/ast/Schema;)V } public final class com/apollographql/apollo3/execution/GraphQLRequest : com/apollographql/apollo3/execution/GraphQLRequestResult { @@ -94,9 +85,6 @@ public abstract interface class com/apollographql/apollo3/execution/Instrumentat } public abstract interface class com/apollographql/apollo3/execution/MainResolver : com/apollographql/apollo3/execution/Resolver { - public abstract fun rootMutationObject ()Ljava/lang/Object; - public abstract fun rootQueryObject ()Ljava/lang/Object; - public abstract fun rootSubscriptionObject ()Ljava/lang/Object; public abstract fun typename (Ljava/lang/Object;)Ljava/lang/String; } @@ -106,6 +94,13 @@ public final class com/apollographql/apollo3/execution/MergedField { public final fun getSelections ()Ljava/util/List; } +public final class com/apollographql/apollo3/execution/NullRoots : com/apollographql/apollo3/execution/Roots { + public static final field INSTANCE Lcom/apollographql/apollo3/execution/NullRoots; + public fun mutation ()Ljava/lang/Object; + public fun query ()Ljava/lang/Object; + public fun subscription ()Ljava/lang/Object; +} + public final class com/apollographql/apollo3/execution/PersistedDocument { public fun (Lcom/apollographql/apollo3/ast/GQLDocument;Ljava/util/List;)V public final fun getDocument ()Lcom/apollographql/apollo3/ast/GQLDocument; @@ -136,11 +131,15 @@ public abstract interface class com/apollographql/apollo3/execution/Resolver { public abstract fun resolve (Lcom/apollographql/apollo3/execution/ResolveInfo;)Ljava/lang/Object; } -public abstract class com/apollographql/apollo3/execution/RootlessResolver : com/apollographql/apollo3/execution/MainResolver { - public fun ()V - public final fun rootMutationObject ()Ljava/lang/Object; - public final fun rootQueryObject ()Ljava/lang/Object; - public final fun rootSubscriptionObject ()Ljava/lang/Object; +public abstract interface class com/apollographql/apollo3/execution/Roots { + public static final field Companion Lcom/apollographql/apollo3/execution/Roots$Companion; + public abstract fun mutation ()Ljava/lang/Object; + public abstract fun query ()Ljava/lang/Object; + public abstract fun subscription ()Ljava/lang/Object; +} + +public final class com/apollographql/apollo3/execution/Roots$Companion { + public final fun create (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)Lcom/apollographql/apollo3/execution/Roots; } public abstract interface class com/apollographql/apollo3/execution/SubscriptionItem { @@ -156,7 +155,7 @@ public final class com/apollographql/apollo3/execution/SubscriptionItemResponse public final fun getResponse ()Lcom/apollographql/apollo3/execution/GraphQLResponse; } -public final class com/apollographql/apollo3/execution/ThrowingResolver : com/apollographql/apollo3/execution/RootlessResolver { +public final class com/apollographql/apollo3/execution/ThrowingResolver : com/apollographql/apollo3/execution/MainResolver { public static final field INSTANCE Lcom/apollographql/apollo3/execution/ThrowingResolver; public fun resolve (Lcom/apollographql/apollo3/execution/ResolveInfo;)Ljava/lang/Object; public fun typename (Ljava/lang/Object;)Ljava/lang/String; diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/ExecutableSchema.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/ExecutableSchema.kt index 5a4e0f0cb58..7b40dcf2170 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/ExecutableSchema.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/ExecutableSchema.kt @@ -26,14 +26,16 @@ class ExecutableSchema internal constructor( private val instrumentations: List, private val mainResolver: MainResolver, private val adapterRegistry: CustomScalarAdapters, + private val roots: Roots, ) { class Builder { - var persistedDocumentCache: PersistedDocumentCache? = null - var instrumentations = mutableListOf() - var resolver: MainResolver? = null - var adapterRegistry: CustomScalarAdapters? = null - var schema: Schema? = null + private var persistedDocumentCache: PersistedDocumentCache? = null + private var instrumentations = mutableListOf() + private var resolver: MainResolver? = null + private var adapterRegistry: CustomScalarAdapters? = null + private var schema: Schema? = null + private var roots: Roots? = null fun persistedDocumentCache(persistedDocumentCache: PersistedDocumentCache?): Builder = apply { this.persistedDocumentCache = persistedDocumentCache @@ -59,6 +61,10 @@ class ExecutableSchema internal constructor( this.resolver = mainResolver } + fun roots(roots: Roots): Builder = apply { + this.roots = roots + } + fun build(): ExecutableSchema { return ExecutableSchema( schema ?: error("A schema is required to build an ExecutableSchema"), @@ -66,6 +72,7 @@ class ExecutableSchema internal constructor( instrumentations, resolver ?: ThrowingResolver, adapterRegistry ?: CustomScalarAdapters.Empty, + roots ?: NullRoots ) } } @@ -201,6 +208,7 @@ class ExecutableSchema internal constructor( mainResolver = mainResolver, adapters = adapterRegistry, instrumentations = instrumentations, + roots = roots ) ) } diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/OperationExecutor.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/OperationExecutor.kt index aa325c0c734..9e64e50d92c 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/OperationExecutor.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/OperationExecutor.kt @@ -36,6 +36,7 @@ internal class OperationExecutor( val mainResolver: MainResolver, val adapters: CustomScalarAdapters, val instrumentations: List, + val roots: Roots, ) { private var errors = mutableListOf() @@ -48,8 +49,8 @@ internal class OperationExecutor( return errorResponse("'${operationDefinition.operationType}' is not supported") } val rootObject = when (operationDefinition.operationType) { - "query" -> mainResolver.rootQueryObject() - "mutation" -> mainResolver.rootMutationObject() + "query" -> roots.query() + "mutation" -> roots.mutation() "subscription" -> error("Use executeSubscription() to execute subscriptions") else -> error("Unknown operation type '${operationDefinition.operationType}") } @@ -82,7 +83,7 @@ internal class OperationExecutor( return errorFlow("'${operationDefinition.operationType}' is not supported") } val rootObject = when (operationDefinition.operationType) { - "subscription" -> mainResolver.rootSubscriptionObject() + "subscription" -> roots.subscription() else -> return errorFlow("Unknown operation type '${operationDefinition.operationType}.") } diff --git a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/Resolver.kt b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/Resolver.kt index 0a7a9ff52a2..480c15a67e6 100644 --- a/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/Resolver.kt +++ b/libraries/apollo-execution/src/commonMain/kotlin/com/apollographql/apollo3/execution/Resolver.kt @@ -28,29 +28,45 @@ fun interface Resolver { /** * A [Resolver] that also has global knowledge about the graph and is able to resolve the typename of any given instance - * as well as provide root objects to bootstrap the execution */ interface MainResolver: Resolver { fun typename(obj: Any): String? +} + +interface Roots { + fun query(): Any? + fun mutation(): Any? + fun subscription(): Any? + + companion object { + fun create(queryObject: () -> Any?, mutationObject: (() -> Any?)?, subscriptionObject: (() -> Any?)?): Roots { + return object : Roots { + override fun query(): Any? { + return queryObject() + } + + override fun mutation(): Any? { + return mutationObject?.invoke() + } - fun rootQueryObject(): Any? - fun rootMutationObject(): Any? - fun rootSubscriptionObject(): Any? + override fun subscription(): Any? { + return subscriptionObject?.invoke() + } + } + } + } } -/** - * a [MainResolver] that doesn't need root objects to start resolving a graph - */ -abstract class RootlessResolver: MainResolver { - final override fun rootQueryObject(): Any? { +object NullRoots: Roots { + override fun query(): Any? { return null } - final override fun rootMutationObject(): Any? { + override fun mutation(): Any? { return null } - final override fun rootSubscriptionObject(): Any? { + override fun subscription(): Any? { return null } } @@ -59,7 +75,7 @@ abstract class RootlessResolver: MainResolver { * A resolver that will always throw * Only useful to test introspection and/or errors */ -object ThrowingResolver: RootlessResolver() { +object ThrowingResolver: MainResolver { override fun typename(obj: Any): String? { TODO("Not implemented") } diff --git a/libraries/apollo-execution/src/commonTest/kotlin/test/ExecutionTest.kt b/libraries/apollo-execution/src/commonTest/kotlin/test/ExecutionTest.kt index 2466f324da3..116720ebb46 100644 --- a/libraries/apollo-execution/src/commonTest/kotlin/test/ExecutionTest.kt +++ b/libraries/apollo-execution/src/commonTest/kotlin/test/ExecutionTest.kt @@ -10,12 +10,12 @@ import com.apollographql.apollo3.ast.GQLType import com.apollographql.apollo3.ast.Schema import com.apollographql.apollo3.execution.ExecutableSchema import com.apollographql.apollo3.execution.GraphQLRequest +import com.apollographql.apollo3.execution.MainResolver import com.apollographql.apollo3.execution.ResolveInfo import com.apollographql.apollo3.execution.Resolver -import com.apollographql.apollo3.execution.RootlessResolver import kotlin.test.Test -private val randomResolver = object : RootlessResolver() { +private val randomResolver = object : MainResolver { fun GQLType.randomValue(schema: Schema): Any? { return when (this) { is GQLNonNullType -> type.randomValue(schema) @@ -70,7 +70,7 @@ class ExecutionTest { } """.trimIndent() - val simpleMainResolver = object : RootlessResolver() { + val simpleMainResolver = object : MainResolver { override fun resolve(resolveInfo: ResolveInfo): Any? { if (resolveInfo.parentType != "Query" || resolveInfo.fieldName != "foo") return null return Resolver { 42 } diff --git a/libraries/apollo-ksp/src/main/kotlin/com/apollographql/apollo3/ksp/ApolloProcessor.kt b/libraries/apollo-ksp/src/main/kotlin/com/apollographql/apollo3/ksp/ApolloProcessor.kt index 6f5d00398a8..656f49da114 100644 --- a/libraries/apollo-ksp/src/main/kotlin/com/apollographql/apollo3/ksp/ApolloProcessor.kt +++ b/libraries/apollo-ksp/src/main/kotlin/com/apollographql/apollo3/ksp/ApolloProcessor.kt @@ -19,6 +19,7 @@ import com.apollographql.apollo3.compiler.ir.IrGraphqlTargetArgument import com.apollographql.apollo3.compiler.ir.IrTargetArgument import com.apollographql.apollo3.compiler.ir.IrTargetField import com.apollographql.apollo3.compiler.ir.IrTargetObject +import com.google.devtools.ksp.getConstructors import com.google.devtools.ksp.isPrivate import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies @@ -244,6 +245,7 @@ class ApolloProcessor( targetClassName = entry.value.className, fields = fields.toList(), isSingleton = entry.value.classDeclaration.classKind == ClassKind.OBJECT, + hasNoArgsConstructor = entry.value.classDeclaration.hasNoArgsConstructor(), operationType = operationType ) } @@ -273,6 +275,12 @@ class ApolloProcessor( } } +private fun KSClassDeclaration.hasNoArgsConstructor(): Boolean { + return getConstructors().any { + it.parameters.isEmpty() + } +} + private fun KSAnnotated.graphqlName(): String? { return findAnnotation("GraphQLName")?.getArgumentValue("name") } diff --git a/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/SampleServer.kt b/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/SampleServer.kt index a4923131058..95d91355a06 100644 --- a/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/SampleServer.kt +++ b/tests/sample-server/src/main/kotlin/com/apollographql/apollo/sample/server/SampleServer.kt @@ -46,6 +46,7 @@ import org.http4k.websocket.WsMessage import org.http4k.websocket.WsResponse import org.http4k.websocket.WsStatus import sample.server.execution.SampleserverAdapterRegistry +import sample.server.execution.SampleserverExecutableSchemaBuilder import sample.server.execution.SampleserverResolver import java.io.Closeable import java.time.Duration @@ -60,10 +61,7 @@ fun ExecutableSchema(): ExecutableSchema { .toGQLDocument() .toSchema() - return ExecutableSchema.Builder() - .schema(schema) - .resolver(SampleserverResolver()) - .adapterRegistry(SampleserverAdapterRegistry) + return SampleserverExecutableSchemaBuilder(schema) .build() }