Skip to main content

Context

Many GraphQL implementations provide some method to pass query-wide parameters around in the graph. gql has no such concept, it is rather a by-product of being written in tagless style.

MTL

We can emulate context by using a ReaderT/Kleisli monad transformer from cats. Writing ReaderT/Kleisli everywhere is tedious, instead consider opting for cats.mtl.Ask:

import gql._
import gql.dsl.all._
import gql.ast._
import cats.mtl.Ask
import cats._
import cats.data._
import cats.implicits._
import io.circe._
import cats.effect._
import cats.effect.unsafe.implicits.global
import io.circe.syntax._

final case class Context(
userId: String
)

def queries[F[_]: Functor](implicit A: Ask[F, Context]): Type[F, Unit] =
tpe[F, Unit](
"Query",
"me" -> eff(_ => A.ask.map(_.userId))
)

type G[A] = Kleisli[IO, Context, A]

def query = """
query {
me
}
"""

Statistics[IO].flatMap{ stats =>
val schema =
Schema.query(stats.mapK(Kleisli.liftK[IO, Context]))(queries[G])

Compiler[G].compile(schema, query) match {
case Right(Application.Query(fa)) =>
fa
.run(Context("john_doe"))
.map(_.asJson)
}
}.unsafeRunSync()
// res0: Json = JObject(
// value = object[data -> {
// "me" : "john_doe"
// }]
// )

Working in a specific effect

If you are working in a specific effect, you most likely have more tools to work with. For instance, if you are using IO, you can use IOLocal to wire context through your application.


trait Authorized {
def getAuth: IO[Ior[String, Context]]
}

object Authorized {
def fromIOLocal(iol: IOLocal[Option[Context]]) = new Authorized {
def getAuth = iol.get.map{
case None => Ior.Left("You must authorize to perform this action")
case Some(c) => Ior.Right(c)
}
}
}

def makeSchema(implicit auth: Authorized): Schema[IO, Unit, Unit, Unit] = ???

IOLocal[Option[Context]](None).flatMap{ implicit loc =>
implicit val auth = Authorized.fromIOLocal(loc)

def s = makeSchema

def runQueryWithSchema: IO[Unit] = ???

def runAuthorizedQuery(userId: String): IO[Unit] =
loc.set(Some(Context(userId))) >> runQueryWithSchema

runAuthorizedQuery("john_doe")
}