Skip to content

Instantly share code, notes, and snippets.

@bastman
Last active July 5, 2018 04:22
Show Gist options
  • Save bastman/f042ab8eaa76e0535a9c9607b91d32b2 to your computer and use it in GitHub Desktop.
Save bastman/f042ab8eaa76e0535a9c9607b91d32b2 to your computer and use it in GitHub Desktop.
spring-graphql-hack-custom-scalartypes
// see:
// https://github.com/graphql-java/graphql-java-tools/blob/master/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpec.kt
// https://github.com/graphql-java/graphql-spring-boot/blob/master/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.java
package com.example.demo.graphql
import com.coxautodev.graphql.tools.*
import graphql.language.ObjectValue
import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLSchema
import graphql.servlet.GraphQLSchemaProvider
import graphql.servlet.ObjectMapperConfigurer
import org.apache.commons.io.IOUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
import java.io.IOException
import java.io.StringWriter
import java.util.*
@Configuration
@ConditionalOnClass(SchemaParser::class)
class GraphQLJavaToolsAutoConfiguration {
@Autowired(required = false)
private val dictionary: SchemaParserDictionary? = null
@Autowired(required = false)
private val scalars: Array<GraphQLScalarType>? = null
@Autowired(required = false)
private val options: SchemaParserOptions? = null
@Autowired(required = false)
private val objectMapperConfigurer: ObjectMapperConfigurer? = null
@Autowired
private val applicationContext: ApplicationContext? = null
@Bean
//@ConditionalOnBean(GraphQLResolver<Any>::class)
@ConditionalOnMissingBean
@Throws(IOException::class)
fun schemaParser(resolvers: List<GraphQLResolver<*>>): SchemaParser {
val builder = if (dictionary != null) SchemaParserBuilder(dictionary) else SchemaParserBuilder()
val resources = applicationContext!!.getResources("classpath*:**/*.graphqls")
if (resources.size <= 0) {
throw IllegalStateException("No *.graphqls files found on classpath. Please add a graphql schema to the classpath or add a SchemaParser bean to your application context.")
}
for (resource in resources) {
val writer = StringWriter()
IOUtils.copy(resource.inputStream, writer)
builder.schemaString(writer.toString())
}
if (scalars != null) {
builder.scalars(*scalars)
}
if (options != null) {
builder.options(options)
} else if (objectMapperConfigurer != null) {
builder.options(SchemaParserOptions.newOptions().objectMapperConfigurer({ om, _ ->
objectMapperConfigurer.configure(om)
}).build())
}
return builder.resolvers(resolvers)
.scalars(customScalarUUID, customScalarMap)
.build()
}
@Bean
@ConditionalOnBean(SchemaParser::class)
@ConditionalOnMissingBean(GraphQLSchema::class, GraphQLSchemaProvider::class)
fun graphQLSchema(schemaParser: SchemaParser): GraphQLSchema {
return schemaParser.makeExecutableSchema()
}
}
@Component
class Query : GraphQLQueryResolver, ListListResolver<String>() {
fun version() = "1.0.0"
}
abstract class ListListResolver<out E> {
fun listList(): List<List<E>> = listOf(listOf())
}
val customScalarUUID = GraphQLScalarType("UUID", "UUID", object : Coercing<UUID, String> {
override fun serialize(input: Any): String? = when (input) {
is String -> input
is UUID -> input.toString()
else -> null
}
override fun parseValue(input: Any): UUID? = parseLiteral(input)
override fun parseLiteral(input: Any): UUID? = when (input) {
is StringValue -> UUID.fromString(input.value)
else -> null
}
})
val customScalarMap = GraphQLScalarType("customScalarMap", "customScalarMap", object : Coercing<Map<String, Any>, Map<String, Any>> {
@Suppress("UNCHECKED_CAST")
override fun parseValue(input: Any?): Map<String, Any> = input as Map<String, Any>
@Suppress("UNCHECKED_CAST")
override fun serialize(dataFetcherResult: Any?): Map<String, Any> = dataFetcherResult as Map<String, Any>
override fun parseLiteral(input: Any?): Map<String, Any> = (input as ObjectValue).objectFields.associateBy { it.name }.mapValues { (it.value.value as StringValue).value }
})
type Query {
# The API Version
version: String!
}
scalar UUID
type Person {
id: UUID!
title: String!
comment: String!
}
extend type Query {
getPersonById(id: UUID!) : Person
}
package com.example.demo.graphql
import com.coxautodev.graphql.tools.GraphQLQueryResolver
import org.springframework.stereotype.Component
import java.util.*
@Component
class PersonQueryResolver(
private val repo: PersonRepo
) : GraphQLQueryResolver {
@Transactional(readOnly = true)
fun getPersonById(id: UUID): PersonDto = repo[id].toPersonDto()
}
// see:
// https://github.com/graphql-java/graphql-java-tools/blob/master/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpec.kt
// https://github.com/graphql-java/graphql-spring-boot/blob/master/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.java
package com.example.demo.graphql
import com.coxautodev.graphql.tools.GraphQLQueryResolver
import graphql.ErrorType
import graphql.ExceptionWhileDataFetching
import graphql.GraphQLError
import graphql.language.ObjectValue
import graphql.language.SourceLocation
import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.GraphQLScalarType
import graphql.servlet.GraphQLErrorHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
import java.util.*
@Configuration
class GraphQLConfig() {
@Bean
fun scalars(): Array<GraphQLScalarType> = arrayOf(customScalarUUID, customScalarMap)
@Bean
fun errorHandler(): GraphQLErrorHandler =
object : GraphQLErrorHandler {
override fun processErrors(errors: MutableList<GraphQLError>): List<GraphQLError> {
val clientErrors = errors.filter(this::isClientError)
val serverErrors = (errors - clientErrors).map { GraphQLErrorAdapter(it) }
return clientErrors + serverErrors
}
protected fun isClientError(error: GraphQLError): Boolean =
when (error) {
is ExceptionWhileDataFetching,
is Throwable -> false
else -> true
}
}
}
class GraphQLErrorAdapter(private val error: GraphQLError) : GraphQLError {
override fun getExtensions(): Map<String, Any> = error.extensions
override fun getLocations(): List<SourceLocation> = error.locations
override fun getErrorType(): ErrorType = error.errorType
override fun getPath(): List<Any> = error.path
override fun toSpecification(): Map<String, Any> = error.toSpecification()
override fun getMessage(): String = when (error) {
is ExceptionWhileDataFetching -> error.exception.message ?: "ExceptionWhileDataFetching"
else -> error.message
}
}
@Component
class Query : GraphQLQueryResolver, ListListResolver<String>() {
fun version() = "1.0.0"
}
abstract class ListListResolver<out E> {
fun listList(): List<List<E>> = listOf(listOf())
}
val customScalarUUID = GraphQLScalarType("UUID", "UUID", object : Coercing<UUID, String> {
override fun serialize(input: Any): String? = when (input) {
is String -> input
is UUID -> input.toString()
else -> null
}
override fun parseValue(input: Any): UUID? = parseLiteral(input)
override fun parseLiteral(input: Any): UUID? = when (input) {
is StringValue -> UUID.fromString(input.value)
else -> null
}
})
val customScalarMap = GraphQLScalarType("customScalarMap", "customScalarMap", object : Coercing<Map<String, Any>, Map<String, Any>> {
@Suppress("UNCHECKED_CAST")
override fun parseValue(input: Any?): Map<String, Any> = input as Map<String, Any>
@Suppress("UNCHECKED_CAST")
override fun serialize(dataFetcherResult: Any?): Map<String, Any> = dataFetcherResult as Map<String, Any>
override fun parseLiteral(input: Any?): Map<String, Any> = (input as ObjectValue).objectFields.associateBy { it.name }.mapValues { (it.value.value as StringValue).value }
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment