Last active
July 5, 2018 04:22
-
-
Save bastman/f042ab8eaa76e0535a9c9607b91d32b2 to your computer and use it in GitHub Desktop.
spring-graphql-hack-custom-scalartypes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 } | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Query { | |
# The API Version | |
version: String! | |
} | |
scalar UUID | |
type Person { | |
id: UUID! | |
title: String! | |
comment: String! | |
} | |
extend type Query { | |
getPersonById(id: UUID!) : Person | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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