From 56b7a0380275b7ce888ee9dda16489f20607b7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Eyselein?= Date: Wed, 7 Dec 2022 10:11:28 +0100 Subject: [PATCH 1/2] use operation name --- .../com/apurebase/kgraphql/KtorFeature.kt | 7 +- .../kgraphql/schema/DefaultSchema.kt | 20 ++-- .../com/apurebase/kgraphql/schema/Schema.kt | 6 +- .../schema/introspection/SchemaProxy.kt | 14 ++- .../schema/structure/RequestInterpreter.kt | 103 ++++++++++++------ 5 files changed, 102 insertions(+), 48 deletions(-) diff --git a/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt b/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt index 5b1098e9..d72096dc 100644 --- a/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt +++ b/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt @@ -77,12 +77,15 @@ class GraphQL(val schema: Schema) { val ctx = context { config.contextSetup?.invoke(this, call) } - val result = schema.execute(request.query, request.variables.toString(), ctx) + val result = + schema.execute(request.query, request.operationName, request.variables.toString(), ctx) call.respondText(result, contentType = ContentType.Application.Json) } if (config.playground) get { @Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - val playgroundHtml = KtorGraphQLConfiguration::class.java.classLoader.getResource("playground.html").readBytes() + val playgroundHtml = + KtorGraphQLConfiguration::class.java.classLoader.getResource("playground.html") + .readBytes() call.respondBytes(playgroundHtml, contentType = ContentType.Text.Html) } } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt index f1cfebe1..f6c09028 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt @@ -19,10 +19,10 @@ import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.jvm.jvmErasure -class DefaultSchema ( - override val configuration: SchemaConfiguration, - internal val model : SchemaModel -) : Schema , __Schema by model, LookupSchema { +class DefaultSchema( + override val configuration: SchemaConfiguration, + internal val model: SchemaModel +) : Schema, __Schema by model, LookupSchema { companion object { val OPERATION_NAME_PARAM = NameNode("operationName", null) @@ -35,11 +35,17 @@ class DefaultSchema ( DataLoaderPrepared -> DataLoaderPreparedRequestExecutor(this) } - private val requestInterpreter : RequestInterpreter = RequestInterpreter(model) + private val requestInterpreter: RequestInterpreter = RequestInterpreter(model) private val cacheParser: CachingDocumentParser by lazy { CachingDocumentParser(configuration.documentParserCacheMaximumSize) } - override suspend fun execute(request: String, variables: String?, context: Context, options: ExecutionOptions): String = coroutineScope { + override suspend fun execute( + request: String, + operationName: String?, + variables: String?, + context: Context, + options: ExecutionOptions + ): String = coroutineScope { val parsedVariables = variables ?.let { VariablesJson.Defined(configuration.objectMapper, variables) } ?: VariablesJson.Empty() @@ -53,7 +59,7 @@ class DefaultSchema ( val executor = options.executor?.let(this@DefaultSchema::getExecutor) ?: defaultRequestExecutor executor.suspendExecute( - plan = requestInterpreter.createExecutionPlan(document, parsedVariables, options), + plan = requestInterpreter.createExecutionPlan(document, operationName, parsedVariables, options), variables = parsedVariables, context = context ) diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt index 03de52fe..e5ec0142 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt @@ -12,15 +12,17 @@ interface Schema : __Schema { suspend fun execute( @Language("graphql") request: String, + operationName: String? = null, variables: String? = null, context: Context = Context(emptyMap()), options: ExecutionOptions = ExecutionOptions() - ) : String + ): String fun executeBlocking( @Language("graphql") request: String, + operationName: String? = null, variables: String? = null, context: Context = Context(emptyMap()), options: ExecutionOptions = ExecutionOptions() - ) = runBlocking { execute(request, variables, context, options) } + ) = runBlocking { execute(request, operationName, variables, context, options) } } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt index 6daa2cd3..6bda5608 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt @@ -11,8 +11,8 @@ import kotlin.reflect.KType class SchemaProxy( override val configuration: SchemaConfiguration, - var proxiedSchema : LookupSchema? = null -): LookupSchema { + var proxiedSchema: LookupSchema? = null +) : LookupSchema { companion object { const val ILLEGAL_STATE_MESSAGE = "Missing proxied __Schema instance" @@ -49,7 +49,13 @@ class SchemaProxy( override fun inputTypeByName(name: String): Type? = inputTypeByName(name) - override suspend fun execute(request: String, variables: String?, context: Context, options: ExecutionOptions): String { - return getProxied().execute(request, variables, context, options) + override suspend fun execute( + request: String, + operationName: String?, + variables: String?, + context: Context, + options: ExecutionOptions + ): String { + return getProxied().execute(request, operationName, variables, context, options) } } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/RequestInterpreter.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/RequestInterpreter.kt index c9111c16..ae8a6f06 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/RequestInterpreter.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/RequestInterpreter.kt @@ -30,7 +30,7 @@ class RequestInterpreter(val schemaModel: SchemaModel) { // prevent stack overflow private val fragmentsStack = Stack() fun get(node: FragmentSpreadNode): Execution.Fragment? { - if(fragmentsStack.contains(node.name.value)) throw GraphQLError( + if (fragmentsStack.contains(node.name.value)) throw GraphQLError( "Fragment spread circular references are not allowed", node ) @@ -46,7 +46,12 @@ class RequestInterpreter(val schemaModel: SchemaModel) { } } - fun createExecutionPlan(document: DocumentNode, variables: VariablesJson, options: ExecutionOptions): ExecutionPlan { + fun createExecutionPlan( + document: DocumentNode, + requestedOperationName: String?, + variables: VariablesJson, + options: ExecutionOptions + ): ExecutionPlan { val test = document.definitions.filterIsInstance() val operation = test.filterIsInstance().let { operations -> @@ -58,8 +63,10 @@ class RequestInterpreter(val schemaModel: SchemaModel) { if (it.size != operations.size) throw GraphQLError("anonymous operation must be the only defined operation") }.joinToString(prefix = "[", postfix = "]") - val operationName = variables.get(String::class, String::class.starProjectedType, OPERATION_NAME_PARAM) - ?: throw GraphQLError("Must provide an operation name from: $operationNamesFound") + val operationName = requestedOperationName ?: ( + variables.get(String::class, String::class.starProjectedType, OPERATION_NAME_PARAM) + ?: throw GraphQLError("Must provide an operation name from: $operationNamesFound") + ) operations.firstOrNull { it.name?.value == operationName } ?: throw GraphQLError("Must provide an operation name from: $operationNamesFound, found $operationName") @@ -69,8 +76,11 @@ class RequestInterpreter(val schemaModel: SchemaModel) { val root = when (operation.operation) { OperationTypeNode.QUERY -> schemaModel.query - OperationTypeNode.MUTATION -> schemaModel.mutation ?: throw GraphQLError("Mutations are not supported on this schema") - OperationTypeNode.SUBSCRIPTION -> schemaModel.subscription ?: throw GraphQLError("Subscriptions are not supported on this schema") + OperationTypeNode.MUTATION -> schemaModel.mutation + ?: throw GraphQLError("Mutations are not supported on this schema") + + OperationTypeNode.SUBSCRIPTION -> schemaModel.subscription + ?: throw GraphQLError("Subscriptions are not supported on this schema") } val fragmentDefinitionNode = test.filterIsInstance() @@ -79,7 +89,7 @@ class RequestInterpreter(val schemaModel: SchemaModel) { val name = fragmentDef.name!!.value if (fragmentDefinitionNode.count { it.name!!.value == name } > 1) { - throw GraphQLError("There can be only one fragment named $name.", fragmentDef ) + throw GraphQLError("There can be only one fragment named $name.", fragmentDef) } name to (type to fragmentDef.selectionSet) @@ -100,7 +110,12 @@ class RequestInterpreter(val schemaModel: SchemaModel) { private fun handleReturnType(ctx: InterpreterContext, type: Type, requestNode: FieldNode) = handleReturnType(ctx, type, requestNode.selectionSet, requestNode.name) - private fun handleReturnType(ctx: InterpreterContext, type: Type, selectionSet: SelectionSetNode?, propertyName: NameNode? = null): List { + private fun handleReturnType( + ctx: InterpreterContext, + type: Type, + selectionSet: SelectionSetNode?, + propertyName: NameNode? = null + ): List { val children = mutableListOf() if (!selectionSet?.selections.isNullOrEmpty()) { @@ -120,15 +135,22 @@ class RequestInterpreter(val schemaModel: SchemaModel) { private fun handleReturnTypeChildOrFragment(node: SelectionNode, returnType: Type, ctx: InterpreterContext) = returnType.unwrapped().handleSelectionFieldOrFragment(node, ctx) - private fun findFragmentType(fragment: FragmentNode, ctx: InterpreterContext, enclosingType: Type): Execution.Fragment = when(fragment) { + private fun findFragmentType( + fragment: FragmentNode, + ctx: InterpreterContext, + enclosingType: Type + ): Execution.Fragment = when (fragment) { is FragmentSpreadNode -> { ctx.get(fragment) ?: throw throwUnknownFragmentTypeEx(fragment) } + is InlineFragmentNode -> { - val type =if (fragment.directives?.isNotEmpty() == true) { + val type = if (fragment.directives?.isNotEmpty() == true) { enclosingType } else { - schemaModel.queryTypesByName[fragment.typeCondition?.name?.value] ?: throw throwUnknownFragmentTypeEx(fragment) + schemaModel.queryTypesByName[fragment.typeCondition?.name?.value] ?: throw throwUnknownFragmentTypeEx( + fragment + ) } Execution.Fragment( selectionNode = fragment, @@ -139,17 +161,23 @@ class RequestInterpreter(val schemaModel: SchemaModel) { } } - private fun Type.handleSelectionFieldOrFragment(node: SelectionNode, ctx: InterpreterContext): Execution = when (node) { - is FragmentNode -> findFragmentType(node, ctx, this) - is FieldNode -> handleSelection(node, ctx) - } + private fun Type.handleSelectionFieldOrFragment(node: SelectionNode, ctx: InterpreterContext): Execution = + when (node) { + is FragmentNode -> findFragmentType(node, ctx, this) + is FieldNode -> handleSelection(node, ctx) + } - private fun Type.handleSelection(node: FieldNode, ctx: InterpreterContext, variables: List? = null): Execution.Node { + private fun Type.handleSelection( + node: FieldNode, + ctx: InterpreterContext, + variables: List? = null + ): Execution.Node { return when (val field = this[node.name.value]) { null -> throw GraphQLError( "Property ${node.name.value} on $name does not exist", node ) + is Field.Union<*> -> handleUnion(field, node, ctx) else -> { validatePropertyArguments(this, field, node) @@ -168,31 +196,40 @@ class RequestInterpreter(val schemaModel: SchemaModel) { } } - private fun handleUnion(field: Field.Union, selectionNode: FieldNode, ctx: InterpreterContext): Execution.Union { + private fun handleUnion( + field: Field.Union, + selectionNode: FieldNode, + ctx: InterpreterContext + ): Execution.Union { validateUnionRequest(field, selectionNode) - val unionMembersChildren: Map> = field.returnType.possibleTypes.associateWith { possibleType -> - val selections = selectionNode.selectionSet?.selections + val unionMembersChildren: Map> = + field.returnType.possibleTypes.associateWith { possibleType -> + val selections = selectionNode.selectionSet?.selections - val a = selections?.filterIsInstance()?.firstOrNull { - ctx.fragments[it.name.value]?.first?.name == possibleType.name - } + val a = selections?.filterIsInstance()?.firstOrNull { + ctx.fragments[it.name.value]?.first?.name == possibleType.name + } - if (a != null) return@associateWith handleReturnType(ctx, possibleType, ctx.fragments.getValue(a.name.value).second) + if (a != null) return@associateWith handleReturnType( + ctx, + possibleType, + ctx.fragments.getValue(a.name.value).second + ) - val b = selections?.filterIsInstance()?.find { - possibleType.name == it.typeCondition?.name?.value - } + val b = selections?.filterIsInstance()?.find { + possibleType.name == it.typeCondition?.name?.value + } - if (b != null) return@associateWith handleReturnType(ctx, possibleType, b.selectionSet) + if (b != null) return@associateWith handleReturnType(ctx, possibleType, b.selectionSet) - throw GraphQLError( - "Missing type argument for type ${possibleType.name}", - selectionNode - ) - } + throw GraphQLError( + "Missing type argument for type ${possibleType.name}", + selectionNode + ) + } - return Execution.Union ( + return Execution.Union( node = selectionNode, unionField = field, memberChildren = unionMembersChildren, From 8ee908051a7dea9cf0367dfe095b0fa804829316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Eyselein?= Date: Wed, 7 Dec 2022 10:19:40 +0100 Subject: [PATCH 2/2] fix tests --- .../com/apurebase/kgraphql/KtorFeature.kt | 20 +++++++++---------- .../kgraphql/schema/DefaultSchema.kt | 11 +++++----- .../com/apurebase/kgraphql/schema/Schema.kt | 10 +++++----- .../schema/introspection/SchemaProxy.kt | 6 +++--- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt b/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt index d72096dc..b84ee669 100644 --- a/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt +++ b/kgraphql-ktor/src/main/kotlin/com/apurebase/kgraphql/KtorFeature.kt @@ -3,24 +3,19 @@ package com.apurebase.kgraphql import com.apurebase.kgraphql.schema.Schema import com.apurebase.kgraphql.schema.dsl.SchemaBuilder import com.apurebase.kgraphql.schema.dsl.SchemaConfigurationDSL -import io.ktor.server.application.* import io.ktor.http.* +import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* -import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.application.install import io.ktor.server.routing.* import io.ktor.util.* -import java.nio.charset.Charset import kotlinx.coroutines.coroutineScope import kotlinx.serialization.json.* import kotlinx.serialization.json.Json.Default.decodeFromString class GraphQL(val schema: Schema) { - class Configuration: SchemaConfigurationDSL() { + class Configuration : SchemaConfigurationDSL() { fun schema(block: SchemaBuilder.() -> Unit) { schemaBlock = block } @@ -47,7 +42,7 @@ class GraphQL(val schema: Schema) { } - companion object Feature: Plugin { + companion object Feature : Plugin { override val key = AttributeKey("KGraphQL") private val rootFeature = FeatureInstance("KGraphQL") @@ -57,7 +52,7 @@ class GraphQL(val schema: Schema) { } } - class FeatureInstance(featureKey: String = "KGraphQL"): Plugin { + class FeatureInstance(featureKey: String = "KGraphQL") : Plugin { override val key = AttributeKey(featureKey) @@ -78,7 +73,12 @@ class GraphQL(val schema: Schema) { config.contextSetup?.invoke(this, call) } val result = - schema.execute(request.query, request.operationName, request.variables.toString(), ctx) + schema.execute( + request.query, + request.variables.toString(), + ctx, + operationName = request.operationName + ) call.respondText(result, contentType = ContentType.Application.Json) } if (config.playground) get { diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt index f6c09028..01399c9f 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt @@ -4,11 +4,12 @@ import com.apurebase.kgraphql.Context import com.apurebase.kgraphql.GraphQLError import com.apurebase.kgraphql.configuration.SchemaConfiguration import com.apurebase.kgraphql.request.CachingDocumentParser -import com.apurebase.kgraphql.request.VariablesJson -import com.apurebase.kgraphql.schema.introspection.__Schema import com.apurebase.kgraphql.request.Parser +import com.apurebase.kgraphql.request.VariablesJson import com.apurebase.kgraphql.schema.execution.* -import com.apurebase.kgraphql.schema.execution.Executor.* +import com.apurebase.kgraphql.schema.execution.Executor.DataLoaderPrepared +import com.apurebase.kgraphql.schema.execution.Executor.Parallel +import com.apurebase.kgraphql.schema.introspection.__Schema import com.apurebase.kgraphql.schema.model.ast.NameNode import com.apurebase.kgraphql.schema.structure.LookupSchema import com.apurebase.kgraphql.schema.structure.RequestInterpreter @@ -41,10 +42,10 @@ class DefaultSchema( override suspend fun execute( request: String, - operationName: String?, variables: String?, context: Context, - options: ExecutionOptions + options: ExecutionOptions, + operationName: String?, ): String = coroutineScope { val parsedVariables = variables ?.let { VariablesJson.Defined(configuration.objectMapper, variables) } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt index e5ec0142..0d0de524 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/Schema.kt @@ -12,17 +12,17 @@ interface Schema : __Schema { suspend fun execute( @Language("graphql") request: String, - operationName: String? = null, variables: String? = null, context: Context = Context(emptyMap()), - options: ExecutionOptions = ExecutionOptions() + options: ExecutionOptions = ExecutionOptions(), + operationName: String? = null ): String fun executeBlocking( @Language("graphql") request: String, - operationName: String? = null, variables: String? = null, context: Context = Context(emptyMap()), - options: ExecutionOptions = ExecutionOptions() - ) = runBlocking { execute(request, operationName, variables, context, options) } + options: ExecutionOptions = ExecutionOptions(), + operationName: String? = null, + ) = runBlocking { execute(request, variables, context, options, operationName) } } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt index 6bda5608..1b990844 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/introspection/SchemaProxy.kt @@ -51,11 +51,11 @@ class SchemaProxy( override suspend fun execute( request: String, - operationName: String?, variables: String?, context: Context, - options: ExecutionOptions + options: ExecutionOptions, + operationName: String? ): String { - return getProxied().execute(request, operationName, variables, context, options) + return getProxied().execute(request, variables, context, options, operationName) } }