Closed brezinajn closed 3 months ago
Hi 👋 . This repo is only about an HttpEngine
implementation. Apollo Kotlin supports persisted queries. See https://www.apollographql.com/docs/kotlin/advanced/persisted-queries for more details.
Hi, I'm not sure if we understand each other. My issue with the HttpEngine
integration is there is no way to customize in this case ExecutableSchema::getOperationExecutor
to implement persisted queries functionality.
I just transferred the issue to https://github.com/apollographql/apollo-kotlin-execution/ but I'm actually not sure if this is the good place. Are you building a client or a server?
A server. Since Application::apolloModule
lives in com.apollographql.execution.ktor
this looks like it might be the right place. Thank you
Nice! We're in the right spot 👍
I think you should be able to add persisted queries using ExecutableSchema.Builder.persistedDocumentCache()
:
val executableSchema = MyExecutableSchemaBuilder()
.persistedDocumentCache(InMemoryPersistedDocumentCache())
.build()
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
apolloModule(executableSchema)
}.start(wait = true)
Heads up that this whole repo is in its very early days so use at your own risks
You are right. Thank you. Works fine.
Yeah, I'm aware this is an early implementation. Being fan of Kotlin Apollo client, I'm just paying around, looking for limitations. My toughts so far:
ApplicationCall
(ktor specific) makes authentication not really viable.@GraphQLQueryRoot
has to have an empty constructor. This forces some sort of property based injection for dependencies, which in my opinion is not optimal..queryRoot
method on ExecutableSchema.Builder
Thank you for your time 👍 Closing the issue 🙂
Not having access to ApplicationCall (ktor specific) makes authentication not really viable.
I think you can have global authentication but maybe not per-endpoint? (https://ktor.io/docs/server-auth.html#configure)
Do not hesitate to share the API of what you'd like to see, or even a pull request.
That's not the issue. The issue is not having a call context in the resolver. Let's say I want to enable user to edit his own username (and of course not username of anyone else)
Quick, single file demonstration
package com.example
import com.apollographql.execution.annotation.GraphQLQueryRoot
import com.apollographql.execution.ktor.respondGraphQL
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
fun Application.module() {
val executableSchema = ServiceExecutableSchemaBuilder().build()
authentication {
basic(name = "myauth") {
validate { credentials ->
getUserByCredentials(credentials)?.let { UserIdPrincipal(it.id) }
}
}
}
routing {
authenticate("myauth") {
post("graphql") {
call.respondGraphQL(executableSchema)
}
}
}
}
@GraphQLQueryRoot
class Query {
/**
* Updates own username
*/
fun ApplicationCall.updateOwnUsername(name: String) {
val userId = principal<UserIdPrincipal>()!!.id
updateUsernameById(userId, name)
}
}
data class UserIdPrincipal(val id: Int) : Principal
data class User(val id: Int, val name: String)
fun getUserByCredentials(credentials: UserPasswordCredential): User? {
TODO()
}
fun updateUsernameById(id: Int, name: String) {
TODO()
}
The thing is, without updateOwnUsername
having an access to the context of the call, I'm unable to access the principal, so I can't get info on who's making the request.
If we agree on a way to proceed, I can cook up a PR.
I think the main question is, how to approach the problem of providing context in a server agnostic way, to support both http4k and Ktor.
You can add an ExecutionContext
parameter as a first parameter to any GraphQL function for that purpose.
ExecutionContext
is a CoroutineContext
-like structure that you can use to tunnel any kind of context into the resolver functions. In that case, you can probably tunnel the call in there. And in order to get access to the call, I think you should be able to use lower level APIs like call.respondGraphQL(executableSchema)
.
All in all, something like this (pseudo code completely non-tested):
class CallExecutionContext(val currentCall: Call) : ExecutionContext.Element {
override val key: ExecutionContext.Key<CallExecutionContext> = Key
companion object Key : ExecutionContext.Key<CallExecutionContext>
}
fun Application.myApolloModule(
executableSchema: ExecutableSchema,
path: String = "/graphql",
) {
routing {
post(path) {
call.respondGraphQL(executableSchema, CallExecutionContext(call))
}
}
}
Maybe that execution context could be made available by default, ~that sounds reasonnable~ edit: I'm not sure actually, maybe some users would prefer passing their own custom authentication information there...
Again, apologies there are no docs for this but contributions are definitely welcome!
Works like a charm. You're probably right. It might be better to let users choose level of abstraction that fits their needs..
Currently there is no way to hook in an adapter to handle persisted queries. If this issue is out of scope of intended features, please feel free to close it.