-
Notifications
You must be signed in to change notification settings - Fork 222
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add minimal support for resolve function returning IO
- Loading branch information
Showing
10 changed files
with
320 additions
and
76 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
22 changes: 22 additions & 0 deletions
22
...s-effect-experimental/src/main/scala/sangria/catseffect/execution/IOExecutionScheme.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package sangria.catseffect.execution | ||
|
||
import cats.effect.IO | ||
import cats.effect.unsafe.implicits.global | ||
import sangria.execution.{AsyncExecutionScheme, AsyncToFuture} | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
/** Prepare an [[sangria.execution.ExecutionScheme]] for [[IO]]. If you want to use another effect, | ||
* use the same bricks to build your own. | ||
*/ | ||
object IOExecutionScheme { | ||
// sangria is using an implicit ExecutionContext at different places. | ||
// For the moment, we need to expose one. | ||
implicit val ec: ExecutionContext = global.compute | ||
|
||
// ideally we would need only this. | ||
implicit val asyncExecutionScheme: AsyncExecutionScheme[IO] = | ||
new AsyncExecutionScheme[IO](new AsyncToFuture[IO] { | ||
override def toFuture[A](f: IO[A]): Future[A] = f.unsafeToFuture() | ||
}) | ||
} |
18 changes: 18 additions & 0 deletions
18
modules/cats-effect-experimental/src/main/scala/sangria/catseffect/schema/AsyncValue.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package sangria.catseffect.schema | ||
|
||
import cats.effect.Async | ||
import sangria.schema.LeafAction | ||
|
||
import scala.concurrent.ExecutionContext | ||
import scala.language.implicitConversions | ||
|
||
case class AsyncValue[Ctx, Val, F[_]: Async](value: F[Val]) extends LeafAction[Ctx, Val] { | ||
override def map[NewVal](fn: Val => NewVal)(implicit | ||
ec: ExecutionContext): AsyncValue[Ctx, NewVal, F] = | ||
new AsyncValue(Async[F].map(value)(fn)) | ||
} | ||
|
||
object AsyncValue { | ||
implicit def asyncAction[Ctx, Val, F[_]: Async](value: F[Val]): LeafAction[Ctx, Val] = | ||
AsyncValue(value) | ||
} |
31 changes: 31 additions & 0 deletions
31
modules/cats-effect-experimental/src/main/scala/sangria/execution/AsyncExecutionScheme.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package sangria.execution | ||
|
||
import cats.effect.Async | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
/** An [[ExecutionScheme]] that is capable of using [[sangria.catseffect.schema.AsyncValue]]. | ||
* | ||
* Its result is an [[Async]]. | ||
*/ | ||
class AsyncExecutionScheme[F[_]: Async]( | ||
val asyncToFuture: AsyncToFuture[F] | ||
) extends ExecutionScheme { | ||
private val asyncF: Async[F] = Async[F] | ||
|
||
override type Result[Ctx, Res] = F[Res] | ||
|
||
override def failed[Ctx, Res](error: Throwable): Result[Ctx, Res] = asyncF.raiseError(error) | ||
|
||
override def onComplete[Ctx, Res](result: Result[Ctx, Res])(op: => Unit)(implicit | ||
ec: ExecutionContext): Result[Ctx, Res] = | ||
asyncF.flatMap(result)(r => asyncF.map(asyncF.delay(op))(_ => r)) | ||
|
||
override def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])( | ||
implicit ec: ExecutionContext): Result[Ctx, Res] = | ||
asyncF.flatMap(asyncF.fromFuture(asyncF.delay(future)))(resultFn) | ||
|
||
override val resolverBuilder: ResolverBuilder = new AsyncResolverBuilder[F](asyncToFuture) | ||
|
||
override def extended: Boolean = false | ||
} |
169 changes: 169 additions & 0 deletions
169
modules/cats-effect-experimental/src/main/scala/sangria/execution/AsyncResolver.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package sangria.execution | ||
|
||
import cats.effect.Async | ||
import sangria.ast | ||
import sangria.ast.{Document, SourceMapper} | ||
import sangria.catseffect.schema.AsyncValue | ||
import sangria.execution.deferred.DeferredResolver | ||
import sangria.marshalling.ResultMarshaller | ||
import sangria.schema._ | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
/** The [[AsyncResolver]] is using the [[FutureResolver]] under the hood. So we need a way to | ||
* transform our [[Async]] into a [[Future]] for now. | ||
*/ | ||
trait AsyncToFuture[F[_]] { | ||
def toFuture[A](f: F[A]): Future[A] | ||
} | ||
|
||
private[execution] class AsyncResolverBuilder[F[_]: Async](asyncToFuture: AsyncToFuture[F]) | ||
extends ResolverBuilder { | ||
override def build[Ctx]( | ||
marshaller: ResultMarshaller, | ||
middlewareCtx: MiddlewareQueryContext[Ctx, _, _], | ||
schema: Schema[Ctx, _], | ||
valueCollector: ValueCollector[Ctx, _], | ||
variables: Map[String, VariableValue], | ||
fieldCollector: FieldCollector[Ctx, _], | ||
userContext: Ctx, | ||
exceptionHandler: ExceptionHandler, | ||
deferredResolver: DeferredResolver[Ctx], | ||
sourceMapper: Option[SourceMapper], | ||
deprecationTracker: DeprecationTracker, | ||
middleware: List[(Any, Middleware[Ctx])], | ||
maxQueryDepth: Option[Int], | ||
deferredResolverState: Any, | ||
preserveOriginalErrors: Boolean, | ||
validationTiming: TimeMeasurement, | ||
queryReducerTiming: TimeMeasurement, | ||
queryAst: Document)(implicit executionContext: ExecutionContext): Resolver[Ctx] = | ||
new AsyncResolver[Ctx, F]( | ||
marshaller, | ||
middlewareCtx, | ||
schema, | ||
valueCollector, | ||
variables, | ||
fieldCollector, | ||
userContext, | ||
exceptionHandler, | ||
deferredResolver, | ||
sourceMapper, | ||
deprecationTracker, | ||
middleware, | ||
maxQueryDepth, | ||
deferredResolverState, | ||
preserveOriginalErrors, | ||
validationTiming, | ||
queryReducerTiming, | ||
queryAst, | ||
asyncToFuture | ||
) | ||
} | ||
|
||
/** The [[Resolver]] that is used to resolve [[AsyncValue]]. | ||
* | ||
* For now, it's using the [[FutureResolver]] under the hood. Later, we can update its | ||
* implementation to avoid using any [[Future]]. | ||
*/ | ||
private[execution] class AsyncResolver[Ctx, F[_]: Async]( | ||
val marshaller: ResultMarshaller, | ||
middlewareCtx: MiddlewareQueryContext[Ctx, _, _], | ||
schema: Schema[Ctx, _], | ||
valueCollector: ValueCollector[Ctx, _], | ||
variables: Map[String, VariableValue], | ||
fieldCollector: FieldCollector[Ctx, _], | ||
userContext: Ctx, | ||
exceptionHandler: ExceptionHandler, | ||
deferredResolver: DeferredResolver[Ctx], | ||
sourceMapper: Option[SourceMapper], | ||
deprecationTracker: DeprecationTracker, | ||
middleware: List[(Any, Middleware[Ctx])], | ||
maxQueryDepth: Option[Int], | ||
deferredResolverState: Any, | ||
preserveOriginalErrors: Boolean, | ||
validationTiming: TimeMeasurement, | ||
queryReducerTiming: TimeMeasurement, | ||
queryAst: ast.Document, | ||
asyncToFuture: AsyncToFuture[F] | ||
)(implicit executionContext: ExecutionContext) | ||
extends Resolver[Ctx] { | ||
|
||
private val asyncF: Async[F] = Async[F] | ||
|
||
private val delegate = new FutureResolver[Ctx]( | ||
marshaller, | ||
middlewareCtx, | ||
schema, | ||
valueCollector, | ||
variables, | ||
fieldCollector, | ||
userContext, | ||
exceptionHandler, | ||
deferredResolver, | ||
sourceMapper, | ||
deprecationTracker, | ||
middleware, | ||
maxQueryDepth, | ||
deferredResolverState, | ||
preserveOriginalErrors, | ||
validationTiming, | ||
queryReducerTiming, | ||
queryAst | ||
) { del => | ||
override protected def resolveLeafAction( | ||
path: ExecutionPath, | ||
tpe: ObjectType[Ctx, _], | ||
userCtx: Ctx, | ||
astFields: Vector[ast.Field], | ||
field: Field[Ctx, _], | ||
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])( | ||
action: LeafAction[Ctx, Any]): (ast.Field, Resolve) = | ||
action match { | ||
case a: AsyncValue[Ctx, Any, F] => | ||
val f = asyncToFuture.toFuture[Any](a.value) | ||
super.resolveFutureValue(path, tpe, userCtx, astFields, field, updateCtx)(FutureValue(f)) | ||
|
||
case action: StandardLeafAction[Ctx, Any] => | ||
super.resolveStandardLeafAction(path, tpe, userCtx, astFields, field, updateCtx)(action) | ||
|
||
case other => unresolvableLeafAction(path, tpe, astFields, updateCtx)(other) | ||
} | ||
|
||
override protected def handleScheme( | ||
result: Future[((Vector[RegisteredError], del.marshaller.Node), Ctx)], | ||
scheme: ExecutionScheme): scheme.Result[Ctx, del.marshaller.Node] = | ||
scheme match { | ||
case s: AsyncExecutionScheme[F] => | ||
val r: F[((Vector[RegisteredError], del.marshaller.Node), Ctx)] = | ||
asyncF.fromFuture(asyncF.delay(result)) | ||
handleSchemeF(r, s).asInstanceOf[scheme.Result[Ctx, del.marshaller.Node]] | ||
|
||
case _ => | ||
super.handleScheme(result, scheme) | ||
} | ||
|
||
private def handleSchemeF( | ||
result: F[((Vector[RegisteredError], del.marshaller.Node), Ctx)], | ||
scheme: AsyncExecutionScheme[F]): scheme.Result[Ctx, del.marshaller.Node] = | ||
asyncF.map(result) { case ((_, res), _) => res } | ||
} | ||
|
||
override def resolveFieldsPar(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( | ||
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = | ||
delegate | ||
.resolveFieldsPar(tpe, value, fields)(scheme) | ||
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]] | ||
|
||
override def resolveFieldsSeq(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( | ||
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = | ||
delegate | ||
.resolveFieldsSeq(tpe, value, fields)(scheme) | ||
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]] | ||
|
||
override def resolveFieldsSubs(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( | ||
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = | ||
delegate | ||
.resolveFieldsSubs(tpe, value, fields)(scheme) | ||
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]] | ||
} |
This file contains 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
Oops, something went wrong.