All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.kickstart.tools.SchemaParserBuilder.kt Maven / Gradle / Ivy

package graphql.kickstart.tools

import graphql.language.Definition
import graphql.language.Document
import graphql.parser.Parser
import graphql.schema.GraphQLScalarType
import graphql.schema.idl.RuntimeWiring
import graphql.schema.idl.SchemaDirectiveWiring
import org.antlr.v4.runtime.RecognitionException
import org.antlr.v4.runtime.misc.ParseCancellationException
import kotlin.reflect.KClass

/**
 * @author Andrew Potter
 */
class SchemaParserBuilder {

    private val dictionary = SchemaParserDictionary()
    private val schemaString = StringBuilder()
    private val files = mutableListOf()
    private val resolvers = mutableListOf>()
    private val scalars = mutableListOf()
    private val runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring()
    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 {
        files.add(filename)
    }

    /**
     * Add a GraphQL schema string directly.
     */
    fun schemaString(string: String) = this.apply {
        if (schemaString.isNotEmpty()) {
            schemaString.append("\n")
        }
        schemaString.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 scalars to the parser's dictionary.
     */
    fun scalars(scalars: List) = this.apply {
        this.scalars.addAll(scalars)
    }

    fun directive(name: String, directive: SchemaDirectiveWiring) = this.apply {
        this.runtimeWiringBuilder.directive(name, directive)
    }

    fun directiveWiring(directive: SchemaDirectiveWiring) = this.apply {
        this.runtimeWiringBuilder.directiveWiring(directive)
    }

    /**
     * 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(name: String, clazz: KClass<*>) = 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(clazz: KClass<*>) = 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(vararg dictionary: KClass<*>) = this.apply {
        this.dictionary.add(*dictionary)
    }

    /**
     * Add arbitrary classes to the parser's dictionary.
     */
    fun dictionary(dictionary: Collection>) = this.apply {
        this.dictionary.add(dictionary)
    }

    fun options(options: SchemaParserOptions) = this.apply {
        this.options = options
    }

    /**
     * Scan for classes with the supplied schema and dictionary.  Used for testing.
     */
    private fun scan(): ScannedSchemaObjects {
        val definitions = appendDynamicDefinitions(parseDefinitions())
        val customScalars = scalars.associateBy { it.name }

        return SchemaClassScanner(dictionary.getDictionary(), definitions, resolvers, customScalars, options)
            .scanForClasses()
    }

    private fun parseDefinitions() = parseDocuments().flatMap { it.definitions }

    private fun appendDynamicDefinitions(baseDefinitions: List>): List> {
        val definitions = baseDefinitions.toMutableList()
        options.typeDefinitionFactories.forEach { definitions.addAll(it.create(definitions)) }
        return definitions.toList()
    }

    private fun parseDocuments(): List {
        val parser = Parser()
        val documents = mutableListOf()
        try {
            files.forEach { documents.add(parser.parseDocument(readFile(it), it)) }

            if (schemaString.isNotEmpty()) {
                documents.add(parser.parseDocument(schemaString.toString()))
            }
        } catch (pce: ParseCancellationException) {
            val cause = pce.cause
            if (cause != null && cause is RecognitionException) {
                throw InvalidSchemaError(pce, cause)
            } else {
                throw pce
            }
        }
        return documents
    }

    private fun readFile(filename: String): String {
        return java.io.BufferedReader(java.io.InputStreamReader(
            object : Any() {}.javaClass.classLoader.getResourceAsStream(filename)
                ?: throw java.io.FileNotFoundException("classpath:$filename")
        )).readText()
    }

    /**
     * Build the parser with the supplied schema and dictionary.
     */
    fun build() = SchemaParser(scan(), options, runtimeWiringBuilder.build())
}

class InvalidSchemaError(pce: ParseCancellationException, private val recognitionException: RecognitionException) : RuntimeException(pce) {
    override val message: String?
        get() = "Invalid schema provided (${recognitionException.javaClass.name}) at: ${recognitionException.offendingToken}"
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy