graphql-kotlin Custom Scalars Type 예제 코드 추가 (#15)

* build : graphql-java-extended-scalars 의존성 추가

* feat : GraphQL 에서 기본적으로 지원하지 않는 Long, java.time 관련 타입 Scalars 생성 훅 설정 추가

* refactor : Long, LocalDateTime 타입 프로퍼티 추가

* feat : @GraphQLName 어노테이션 예제 코드 추가
This commit is contained in:
Colt
2022-08-04 12:20:10 +09:00
committed by GitHub
parent 6aa4913294
commit 8a0df3a322
4 changed files with 118 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ dependencies {
testImplementation("io.projectreactor:reactor-test")
implementation("com.expediagroup", "graphql-kotlin-spring-server", "6.0.0")
implementation("com.graphql-java:graphql-java-extended-scalars:18.1")
}
tasks.withType<KotlinCompile> {

View File

@@ -0,0 +1,107 @@
package com.banjjoknim.graphqlkotlin.configuration
import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
import graphql.language.StringValue
import graphql.scalars.ExtendedScalars
import graphql.scalars.util.Kit.typeName
import graphql.schema.Coercing
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import graphql.schema.CoercingSerializeException
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLType
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import kotlin.reflect.KClass
import kotlin.reflect.KType
/**
* - [Extended Scalars for graphql-java](https://github.com/graphql-java/graphql-java-extended-scalars)
*
* - [Cannot use java.util.Date](https://github.com/ExpediaGroup/graphql-kotlin/discussions/1198)
*
* - [GraphQL Kotlin - Extended Scalars](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/scalars/#common-issues)
*
* - [GraphQL Kotlin - Generator Configuration & Hooks](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/customizing-schemas/generator-config)
*/
@Configuration
class ExtendedScalarsConfiguration {
/**
* 아래와 같이 Bean으로 Hook을 등록해주면 Schema Generator가 Schema를 생성할 때 이 Bean에 정의된 Hook을 이용해서 Schema를 만든다.
*/
@Bean
fun extendedScalarsHooks(): SchemaGeneratorHooks {
return object : SchemaGeneratorHooks {
override fun willGenerateGraphQLType(type: KType): GraphQLType? {
return when (type.classifier as? KClass<*>) {
Long::class -> ExtendedScalars.GraphQLLong
LocalDateTime::class -> localDateTimeScalar()
LocalTime::class -> ExtendedScalars.LocalTime
LocalDate::class -> ExtendedScalars.Date
else -> null
}
}
}
}
/**
* Bean으로 ScalarType을 등록해주지 않으면 어플리케이션 실행시 스키마를 구성하는 단계(스키마에 포함될 타입중에서 LocalDateTime 이 포함되어 있는 경우)에서 아래와 같은 예외가 발생한다.
*
* ```
* graphql.AssertException: All types within a GraphQL schema must have unique names. No two provided types may have the same name.
* No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types). You have redefined the type 'LocalDateTime' from being a 'GraphQLScalarType' to a 'GraphQLScalarType'
* ```
*
* @see graphql.scalars.datetime.DateTimeScalar
*/
@Bean
fun localDateTimeScalar(): GraphQLScalarType? {
val coercing = object : Coercing<LocalDateTime, String> {
override fun serialize(dataFetcherResult: Any): String {
return when (dataFetcherResult) {
is LocalDateTime -> DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(
LocalDateTime.from(dataFetcherResult)
)
is String -> DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(
LocalDateTime.parse(dataFetcherResult)
)
else -> throw CoercingSerializeException(
"Expected something we can convert to 'java.time.LocalDateTime' but was '" +
"${typeName(dataFetcherResult)}'."
)
}
}
override fun parseValue(input: Any): LocalDateTime {
return when (input) {
is LocalDateTime -> input
is String -> LocalDateTime.parse(
input.toString(),
DateTimeFormatter.ISO_LOCAL_DATE_TIME
)
else -> throw CoercingParseValueException(
"Expected a 'String' but was '" + "${typeName(input)}'."
)
}
}
override fun parseLiteral(input: Any): LocalDateTime {
if (input !is StringValue) {
throw CoercingParseLiteralException(
"Expected AST type 'StringValue' but was '${typeName(input)}'."
)
}
return LocalDateTime.parse(input.toString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}
return GraphQLScalarType.newScalar()
.name("LocalDateTime")
.description("Custom LocalDateTime Scalar")
.coercing(coercing)
.build()
}
}

View File

@@ -1,5 +1,9 @@
package com.banjjoknim.graphqlkotlin.person
import java.time.LocalDateTime
data class Person(
var name: String,
var age: Long? = 0L,
var birthDate: LocalDateTime = LocalDateTime.now()
)

View File

@@ -2,9 +2,11 @@ package com.banjjoknim.graphqlkotlin.person
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import com.expediagroup.graphql.generator.annotations.GraphQLName
import com.expediagroup.graphql.server.operations.Query
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import kotlin.random.Random
@Component
class PersonQuery(
@@ -37,4 +39,8 @@ class PersonQuery(
fun findPerson(@GraphQLIgnore @Autowired personRepository: PersonRepository, name: String): Person? {
return personRepository.findPerson(name)
}
@GraphQLDescription("@GraphQLName example")
@GraphQLName("somePerson")
fun randomPerson(name: String): Person = Person(name = name, age = Random.nextLong())
}