com.coxautodev.graphql.tools.SchemaParserBuilder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphql-java-tools Show documentation
Show all versions of graphql-java-tools Show documentation
Tools to help map a GraphQL schema to existing Java objects.
package com.coxautodev.graphql.tools
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.collect.BiMap
import com.google.common.collect.HashBiMap
import com.google.common.collect.Maps
import graphql.parser.Parser
import graphql.schema.GraphQLScalarType
import org.antlr.v4.runtime.RecognitionException
import org.antlr.v4.runtime.misc.ParseCancellationException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.concurrent.Future
/**
* @author Andrew Potter
*/
class SchemaParserBuilder constructor(private val dictionary: SchemaParserDictionary = SchemaParserDictionary()) {
private val schemaString = StringBuilder()
private val resolvers = mutableListOf>()
private val scalars = mutableListOf()
private var options = SchemaParserOptions.defaultOptions()
/**
* Add GraphQL schema files from the classpath.
*/
fun files(vararg files: String) = this.apply {
files.forEach { this.file(it) }
}
/**
* Add a GraphQL Schema file from the classpath.
*/
fun file(filename: String) = this.apply {
this.schemaString(java.io.BufferedReader(java.io.InputStreamReader(
object : Any() {}.javaClass.classLoader.getResourceAsStream(filename) ?: throw java.io.FileNotFoundException("classpath:$filename")
)).readText())
}
/**
* Add a GraphQL schema string directly.
*/
fun schemaString(string: String) = this.apply {
schemaString.append("\n").append(string)
}
/**
* Add GraphQLResolvers to the parser's dictionary.
*/
fun resolvers(vararg resolvers: GraphQLResolver<*>) = this.apply {
this.resolvers.addAll(resolvers)
}
/**
* Add GraphQLResolvers to the parser's dictionary.
*/
fun resolvers(resolvers: List>) = this.apply {
this.resolvers.addAll(resolvers)
}
/**
* Add scalars to the parser's dictionary.
*/
fun scalars(vararg scalars: GraphQLScalarType) = this.apply {
this.scalars.addAll(scalars)
}
/**
* Add arbitrary classes to the parser's dictionary, overriding the generated type name.
*/
fun dictionary(name: String, clazz: Class<*>) = this.apply {
this.dictionary.add(name, clazz)
}
/**
* Add arbitrary classes to the parser's dictionary, overriding the generated type name.
*/
fun dictionary(dictionary: Map>) = this.apply {
this.dictionary.add(dictionary)
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun dictionary(clazz: Class<*>) = this.apply {
this.dictionary.add(clazz)
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun dictionary(vararg dictionary: Class<*>) = this.apply {
this.dictionary.add(*dictionary)
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun dictionary(dictionary: List>) = this.apply {
this.dictionary.add(dictionary)
}
fun options(options: SchemaParserOptions) = this.apply {
this.options = options
}
/**
* Build the parser with the supplied schema and dictionary.
*/
fun build(): SchemaParser {
val document = try {
Parser().parseDocument(this.schemaString.toString())
} catch (pce: ParseCancellationException) {
val cause = pce.cause
if(cause != null && cause is RecognitionException) {
throw InvalidSchemaError(pce, cause)
} else {
throw pce
}
}
val definitions = document.definitions
val customScalars = scalars.associateBy { it.name }
return SchemaClassScanner(dictionary.getDictionary(), definitions, resolvers, customScalars, options).scanForClasses()
}
}
class InvalidSchemaError(pce: ParseCancellationException, val recognitionException: RecognitionException): RuntimeException(pce) {
override val message: String?
get() = "Invalid schema provided (${recognitionException.javaClass.name}) at: ${recognitionException.offendingToken}"
}
class SchemaParserDictionary {
private val dictionary: BiMap> = HashBiMap.create()
fun getDictionary(): BiMap> = Maps.unmodifiableBiMap(dictionary)
/**
* Add arbitrary classes to the parser's dictionary, overriding the generated type name.
*/
fun add(name: String, clazz: Class<*>) = this.apply {
this.dictionary.put(name, clazz)
}
/**
* Add arbitrary classes to the parser's dictionary, overriding the generated type name.
*/
fun add(dictionary: Map>) = this.apply {
this.dictionary.putAll(dictionary)
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun add(clazz: Class<*>) = this.apply {
this.add(clazz.simpleName, clazz)
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun add(vararg dictionary: Class<*>) = this.apply {
dictionary.forEach { this.add(it) }
}
/**
* Add arbitrary classes to the parser's dictionary.
*/
fun add(dictionary: List>) = this.apply {
dictionary.forEach { this.add(it) }
}
}
data class SchemaParserOptions internal constructor(val genericWrappers: List, val allowUnimplementedResolvers: Boolean, val objectMapperConfigurer: ObjectMapperConfigurer) {
companion object {
@JvmStatic fun newOptions() = Builder()
@JvmStatic fun defaultOptions() = Builder().build()
}
class Builder {
private val genericWrappers: MutableList = mutableListOf()
private var useDefaultGenericWrappers = true
private var allowUnimplementedResolvers = false
private var objectMapperConfigurer: ObjectMapperConfigurer = ObjectMapperConfigurer { _, _ -> }
fun genericWrappers(genericWrappers: List) = this.apply {
this.genericWrappers.addAll(genericWrappers)
}
fun genericWrappers(vararg genericWrappers: GenericWrapper) = this.apply {
this.genericWrappers.addAll(genericWrappers)
}
fun useDefaultGenericWrappers(useDefaultGenericWrappers: Boolean) = this.apply {
this.useDefaultGenericWrappers = useDefaultGenericWrappers
}
fun allowUnimplementedResolvers(allowUnimplementedResolvers: Boolean) = this.apply {
this.allowUnimplementedResolvers = allowUnimplementedResolvers
}
fun objectMapperConfigurer(objectMapperConfigurer: ObjectMapperConfigurer) = this.apply {
this.objectMapperConfigurer = objectMapperConfigurer
}
fun objectMapperConfigurer(objectMapperConfigurer: (ObjectMapper, ObjectMapperConfigurerContext) -> Unit) = this.apply {
this.objectMapperConfigurer(ObjectMapperConfigurer(objectMapperConfigurer))
}
fun build(): SchemaParserOptions {
val wrappers = if(useDefaultGenericWrappers) {
genericWrappers + listOf(
GenericWrapper(Future::class.java, 0),
GenericWrapper(CompletableFuture::class.java, 0),
GenericWrapper(CompletionStage::class.java, 0)
)
} else {
genericWrappers
}
return SchemaParserOptions(wrappers, allowUnimplementedResolvers, objectMapperConfigurer)
}
}
data class GenericWrapper(val type: Class<*>, val index: Int)
}