David Bieregger
08/29/2023, 9:25 PMclass ExposedDaoHook : SchemaGeneratorHooks {
private val graphQLSizedIterable = GraphQLScalarType.newScalar()
.name("SizedIterable")
.description("A custom scalar that converts SizedIterable to List")
.coercing(object : Coercing<SizedIterable<Any>, List<Any>> {
override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): List<Any>? {
return (dataFetcherResult as SizedIterable<Any>).toList()
}
})
.build()
private val graphQLLongEntityId = GraphQLScalarType.newScalar()
.name("LongEntityID")
.description("A custom scalar that converts LongEntityID to Long")
.coercing(object : Coercing<EntityID<Long>, Long> {
override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): Long {
return (dataFetcherResult as EntityID<Long>).value
}
}).build()
private val graphQLUUIDEntityID = GraphQLScalarType.newScalar()
.name("UUIDEntityID")
.description("A custom scalar that converts UUIDEntityID to UUID")
.coercing(object : Coercing<EntityID<UUID>, UUID> {
override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): UUID {
return (dataFetcherResult as EntityID<UUID>).value
}
}).build()
override fun willGenerateGraphQLType(type: KType): GraphQLType? {
return when (type.classifier) {
UUID::class -> ExtendedScalars.UUID
Long::class -> ExtendedScalars.GraphQLLong
EntityID::class ->
when (type.arguments[0].type!!.classifier) {
Long::class -> graphQLLongEntityId
UUID::class -> graphQLUUIDEntityID
else -> null
}
Instant::class -> Scalars.GraphQLString
LocalDate::class -> Scalars.GraphQLString
LocalTime::class -> Scalars.GraphQLString
Duration::class -> Scalars.GraphQLString
SizedIterable::class -> graphQLSizedIterable
else -> null
}
}
private val excludedDaoProperties = listOf("klass", "db", "_readValues", "readValues", "writeValues")
override fun isValidProperty(kClass: KClass<*>, property: KProperty<*>): Boolean {
if (!kClass.allSuperclasses.contains(Entity::class))
return super.isValidProperty(kClass, property)
return !excludedDaoProperties.contains(property.name)
}
val excludedDaoMethods =
listOf("isNewEntity", "delete", "flush", "refresh", "getValue", "setValue", "lookup", "storeWrittenValues")
override fun isValidFunction(kClass: KClass<*>, function: KFunction<*>): Boolean {
val superclasses = kClass.allSuperclasses
if (!superclasses.contains(Entity::class))
return super.isValidFunction(kClass, function)
if (excludedDaoMethods.contains(function.name))
return false
println(function.name)
return true
}
override fun willResolveMonad(type: KType): KType = when (type.classifier) {
SizedIterable::class -> List::class.createType(type.arguments)
else -> type
}
}
This will make the types get serialized correctly. Now we can install the plugin:
install(GraphQL) {
schema {
packages = listOf("com.example")
queries = listOf(
HelloQuery()
)
hooks = ExposedDaoHook()
}
}
val graphQLPlugin = plugin(GraphQL)
routing {
val route = post("graphql") {
newSuspendedTransaction(<http://Dispatchers.IO|Dispatchers.IO>) {
try {
graphQLPlugin.server.execute(call.request)?.let {
call.respond(it)
} ?: call.respond(HttpStatusCode.BadRequest)
} catch (e: UnsupportedOperationException) {
call.respond(HttpStatusCode.MethodNotAllowed)
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest)
}
}
}
route.install(ContentNegotiation) {
jackson()
}
}
Sadly there is currently no way to wrap requests in a transaction. So we need to inline some internal functions of the library.
class HelloQuery : Query {
suspend fun user(id: Long) = UserDao.findById(id)
}
class Users : IdTable() {
var forename = varchar("forename", 256)
var surname = varchar("surname", 256)
}
class UserDao(id: EntityID<Long>) : LongEntity(id) {
var forename by Users.forename
var surname by Users.surname
}
Hope this helps someone that has the same use case, please feel free to ask or enhance my implementation if you see obvious bugs