All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.expediagroup.graphql.server.ktor.GraphQL.kt Maven / Gradle / Ivy
/*
* Copyright 2024 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.expediagroup.graphql.server.ktor
import com.apollographql.federation.graphqljava.tracing.FederatedTracingInstrumentation
import com.expediagroup.graphql.apq.provider.AutomaticPersistedQueriesProvider
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation
import com.expediagroup.graphql.generator.ClasspathTypeResolver
import com.expediagroup.graphql.generator.SchemaGenerator
import com.expediagroup.graphql.generator.SchemaGeneratorConfig
import com.expediagroup.graphql.generator.SimpleTypeResolver
import com.expediagroup.graphql.generator.TopLevelObject
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
import com.expediagroup.graphql.generator.federation.FederatedClasspathTypeResolver
import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorConfig
import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorHooks
import com.expediagroup.graphql.generator.federation.FederatedSimpleTypeResolver
import com.expediagroup.graphql.generator.federation.toFederatedSchema
import com.expediagroup.graphql.generator.internal.state.ClassScanner
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
import com.expediagroup.graphql.server.ktor.subscriptions.KtorGraphQLWebSocketServer
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import graphql.execution.AsyncExecutionStrategy
import graphql.execution.AsyncSerialExecutionStrategy
import graphql.execution.instrumentation.ChainedInstrumentation
import graphql.execution.instrumentation.Instrumentation
import graphql.execution.preparsed.PreparsedDocumentProvider
import graphql.schema.GraphQLSchema
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.BaseApplicationPlugin
import io.ktor.server.response.respond
import io.ktor.util.AttributeKey
import kotlin.reflect.KClass
import graphql.GraphQL as GraphQLEngine
/**
* Ktor plugin that creates GraphQL server based on the provided [GraphQLConfiguration].
*
* A configuration of the `GraphQL` plugin might look as follows:
* 1. Configure and install plugin
* ```kotlin
* install(GraphQL) {
* // your schema, engine and server configuration goes here
* }
* ```
*
* 2. Configure GraphQL routes
* ```kotlin
* routing {
* graphQLPostRoute()
* }
* ```
*
* @param config GraphQL configuration
*/
class GraphQL(config: GraphQLConfiguration) {
private val supportedPackages: List = config.schema.packages ?: throw IllegalStateException("Missing required configuration - packages property is required")
private val typeHierarchy: Map, List>>? = config.schema.typeHierarchy
val schema: GraphQLSchema = if (config.schema.federation.enabled) {
val schemaConfig = FederatedSchemaGeneratorConfig(
supportedPackages = supportedPackages,
topLevelNames = config.schema.topLevelNames,
hooks = config.schema.hooks as? FederatedSchemaGeneratorHooks ?: throw IllegalStateException("Non federated schema generator hooks were specified when generating federated schema"),
dataFetcherFactoryProvider = config.engine.dataFetcherFactoryProvider,
introspectionEnabled = config.engine.introspection.enabled,
typeResolver = if (typeHierarchy != null) {
val entities = config.schema.federation.entities ?: emptyList()
FederatedSimpleTypeResolver(typeHierarchy, entities)
} else {
FederatedClasspathTypeResolver(ClassScanner(supportedPackages))
}
)
toFederatedSchema(
config = schemaConfig,
queries = config.schema.queries.toTopLevelObjects(),
mutations = config.schema.mutations.toTopLevelObjects(),
subscriptions = config.schema.subscriptions.toTopLevelObjects(),
schemaObject = config.schema.schemaObject?.let { TopLevelObject(it) }
)
} else {
val schemaConfig = SchemaGeneratorConfig(
supportedPackages = supportedPackages,
topLevelNames = config.schema.topLevelNames,
hooks = config.schema.hooks,
dataFetcherFactoryProvider = config.engine.dataFetcherFactoryProvider,
introspectionEnabled = config.engine.introspection.enabled,
typeResolver = typeHierarchy?.let { SimpleTypeResolver(it) } ?: ClasspathTypeResolver(ClassScanner(supportedPackages))
)
val generator = SchemaGenerator(schemaConfig)
generator.use { gen ->
gen.generateSchema(
queries = config.schema.queries.toTopLevelObjects(),
mutations = config.schema.mutations.toTopLevelObjects(),
subscriptions = config.schema.subscriptions.toTopLevelObjects(),
schemaObject = config.schema.schemaObject?.let { TopLevelObject(it) }
)
}
}
val engine: GraphQLEngine = GraphQLEngine.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(config.engine.exceptionHandler))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(config.engine.exceptionHandler))
.subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy(config.engine.exceptionHandler))
.valueUnboxer(config.engine.idValueUnboxer)
.also { builder ->
config.engine.executionIdProvider?.let { builder.executionIdProvider(it) }
var preparsedDocumentProvider: PreparsedDocumentProvider? = config.engine.preparsedDocumentProvider
if (config.engine.automaticPersistedQueries.enabled) {
if (preparsedDocumentProvider != null) {
throw IllegalStateException("Custom prepared document provider and APQ specified - disable APQ or don't specify the provider")
} else {
preparsedDocumentProvider = AutomaticPersistedQueriesProvider(config.engine.automaticPersistedQueries.cache)
}
}
preparsedDocumentProvider?.let { builder.preparsedDocumentProvider(it) }
val instrumentations = mutableListOf()
if (config.engine.batching.enabled) {
builder.doNotAutomaticallyDispatchDataLoader()
instrumentations.add(
when (config.engine.batching.strategy) {
GraphQLConfiguration.BatchingStrategy.SYNC_EXHAUSTION -> DataLoaderSyncExecutionExhaustedInstrumentation()
}
)
}
if (config.schema.federation.enabled && config.schema.federation.tracing.enabled) {
instrumentations.add(FederatedTracingInstrumentation(FederatedTracingInstrumentation.Options(config.schema.federation.tracing.debug)))
}
instrumentations.addAll(config.engine.instrumentations)
builder.instrumentation(ChainedInstrumentation(instrumentations))
}
.build()
// TODO cannot override the request handler/server as it requires access to graphql engine
private val requestHandler: GraphQLRequestHandler = GraphQLRequestHandler(
graphQL = engine,
dataLoaderRegistryFactory = config.engine.dataLoaderRegistryFactory
)
val server: KtorGraphQLServer = KtorGraphQLServer(
requestParser = config.server.requestParser,
contextFactory = config.server.contextFactory,
requestHandler = requestHandler
)
val subscriptionServer: KtorGraphQLWebSocketServer by lazy {
KtorGraphQLWebSocketServer(
requestParser = config.server.subscriptions.requestParser,
contextFactory = config.server.subscriptions.contextFactory,
subscriptionHooks = config.server.subscriptions.hooks,
requestHandler = requestHandler,
initTimeoutMillis = config.server.subscriptions.connectionInitTimeout,
objectMapper = jacksonObjectMapper().apply(config.server.jacksonConfiguration)
)
}
companion object Plugin : BaseApplicationPlugin {
override val key: AttributeKey = AttributeKey("GraphQL")
override fun install(pipeline: Application, configure: GraphQLConfiguration.() -> Unit): GraphQL {
val config = GraphQLConfiguration(pipeline.environment.config).apply(configure)
return GraphQL(config)
}
}
}
internal fun List.toTopLevelObjects(): List = this.map {
TopLevelObject(it)
}
internal suspend inline fun KtorGraphQLServer.executeRequest(call: ApplicationCall) =
execute(call.request)?.let {
call.respond(it)
} ?: call.respond(HttpStatusCode.BadRequest)