diff --git a/build.sbt b/build.sbt index 903aa715..f6fe9baa 100644 --- a/build.sbt +++ b/build.sbt @@ -138,7 +138,38 @@ lazy val core = project ProblemFilters.exclude[IncompatibleMethTypeProblem]( "sangria.execution.Resolver.resolveSimpleListValue"), ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.schema.Field.subs"), - ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.schema.Field.apply") + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.schema.Field.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "sangria.execution.ExecutionScheme.effectScheme"), + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Resolver.handleScheme"), + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Resolver.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.processFinalResolve"), + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Resolver.resolveSeq"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.ParentDeferredContext"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.ErrorFieldResolution"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.StandardFieldResolution"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.StreamFieldResolution"), + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Resolver.resolveSubs"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.collectActionsPar"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.resolveActionsPar"), + ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Resolver.resolveValue"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.resolveValue$default$7"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.isUndefinedValue"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.resolveSimpleListValue"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver.nullForNotNullTypeError"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sangria.execution.Resolver#Result.builderValue") ), Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oF"), libraryDependencies ++= Seq( diff --git a/modules/core/src/main/scala/sangria/execution/EffectScheme.scala b/modules/core/src/main/scala/sangria/execution/EffectScheme.scala new file mode 100644 index 00000000..e1c9b6ce --- /dev/null +++ b/modules/core/src/main/scala/sangria/execution/EffectScheme.scala @@ -0,0 +1,28 @@ +package sangria.execution + +import scala.collection.BuildFrom +import scala.concurrent.{ExecutionContext, Future} + +trait EffectScheme { + type F[_] + def map[A, B](eff: F[A])(f: A => B): F[B] + def flatMap[A, B](eff: F[A])(f: A => F[B]): F[B] + def success[A](a: A): F[A] + def sequence[A, CC[X] <: IterableOnce[X]](in: CC[F[A]])(implicit + bf: BuildFrom[CC[F[A]], A, CC[A]]): F[CC[A]] + def fromFuture[A](f: Future[A]): F[A] + def toFuture[A](f: F[A]): Future[A] +} + +class FutureEffectScheme(implicit ec: ExecutionContext) extends EffectScheme { + override type F[A] = Future[A] + + override def map[A, B](eff: Future[A])(f: A => B): Future[B] = eff.map(f) + override def flatMap[A, B](eff: Future[A])(f: A => Future[B]): Future[B] = eff.flatMap(f) + override def success[A](a: A): Future[A] = Future.successful(a) + def sequence[A, CC[X] <: IterableOnce[X]](in: CC[Future[A]])(implicit + bf: BuildFrom[CC[Future[A]], A, CC[A]]): Future[CC[A]] = + Future.sequence(in) + override def fromFuture[A](f: Future[A]): Future[A] = f + override def toFuture[A](f: Future[A]): Future[A] = f +} diff --git a/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala b/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala index 93abf699..68da5565 100644 --- a/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala +++ b/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala @@ -14,12 +14,15 @@ sealed trait ExecutionScheme { def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])(implicit ec: ExecutionContext): Result[Ctx, Res] def extended: Boolean + def effectScheme(ec: ExecutionContext): EffectScheme } object ExecutionScheme extends AlternativeExecutionScheme { implicit object Default extends ExecutionScheme { type Result[Ctx, Res] = Future[Res] + override def effectScheme(ec: ExecutionContext): EffectScheme = new FutureEffectScheme()(ec) + def failed[Ctx, Res](error: Throwable): Result[Ctx, Res] = Future.failed(error) @@ -46,9 +49,11 @@ trait EffectOps[F[_]] { @ApiMayChange class EffectBasedExecutionScheme[F[_]]( - ops: EffectOps[F] + ops: EffectOps[F], + effect: EffectScheme ) extends ExecutionScheme { override type Result[Ctx, Res] = F[Res] + override def effectScheme(ec: ExecutionContext): EffectScheme = effect override def failed[Ctx, Res](error: Throwable): Result[Ctx, Res] = ops.failed(error) override def onComplete[Ctx, Res](result: Result[Ctx, Res])(op: => Unit)(implicit @@ -56,9 +61,6 @@ class EffectBasedExecutionScheme[F[_]]( override def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])( implicit ec: ExecutionContext): Result[Ctx, Res] = ops.flatMapFuture(future)(resultFn) - def mapEffect[Ctx, Res, T](future: Future[(Ctx, T)])(f: (Ctx, T) => Res)(implicit - ec: ExecutionContext): F[Res] = - ops.map(future) { case (ctx, in) => f(ctx, in) } override def extended: Boolean = false } @@ -70,6 +72,7 @@ trait AlternativeExecutionScheme { implicit object Extended extends ExecutionScheme { type Result[Ctx, T] = Future[ExecutionResult[Ctx, T]] + override def effectScheme(ec: ExecutionContext): EffectScheme = new FutureEffectScheme()(ec) def failed[Ctx, Res](error: Throwable): Result[Ctx, Res] = Future.failed(error) @@ -93,6 +96,7 @@ trait AlternativeExecutionScheme { } = new ExecutionScheme with StreamBasedExecutionScheme[S] { type Result[Ctx, T] = S[T] + override def effectScheme(ec: ExecutionContext): EffectScheme = new FutureEffectScheme()(ec) def subscriptionStream = stream def extended = false @@ -115,6 +119,7 @@ trait AlternativeExecutionScheme { } = new ExecutionScheme with StreamBasedExecutionScheme[S] { type Result[Ctx, T] = S[ExecutionResult[Ctx, T]] + override def effectScheme(ec: ExecutionContext): EffectScheme = new FutureEffectScheme()(ec) def subscriptionStream = stream def extended = true diff --git a/modules/core/src/main/scala/sangria/execution/Executor.scala b/modules/core/src/main/scala/sangria/execution/Executor.scala index 87d0c804..76f66da4 100644 --- a/modules/core/src/main/scala/sangria/execution/Executor.scala +++ b/modules/core/src/main/scala/sangria/execution/Executor.scala @@ -268,7 +268,7 @@ case class Executor[Ctx, Root]( validationTiming, queryReducerTiming, queryAst - ) + )(executionContext, scheme.effectScheme(executionContext)) val result = operation.operationType match { diff --git a/modules/core/src/main/scala/sangria/execution/Resolver.scala b/modules/core/src/main/scala/sangria/execution/Resolver.scala index 9fdb0c16..91fd3afc 100644 --- a/modules/core/src/main/scala/sangria/execution/Resolver.scala +++ b/modules/core/src/main/scala/sangria/execution/Resolver.scala @@ -8,6 +8,7 @@ import sangria.ast.SourceMapper import sangria.schema._ import sangria.streaming.SubscriptionStream +import scala.annotation.tailrec import scala.collection.immutable.VectorBuilder import scala.concurrent.{ExecutionContext, Future, Promise} import scala.util.control.NonFatal @@ -32,7 +33,7 @@ class Resolver[Ctx]( validationTiming: TimeMeasurement, queryReducerTiming: TimeMeasurement, queryAst: ast.Document -)(implicit executionContext: ExecutionContext) { +)(implicit executionContext: ExecutionContext, effectScheme: EffectScheme) { val resultResolver = new ResultResolver(marshaller, exceptionHandler, preserveOriginalErrors) val toScalarMiddleware = Middleware.composeToScalarMiddleware(middleware.map(_._2), userContext) @@ -45,17 +46,22 @@ class Resolver[Ctx]( collectActionsPar(ExecutionPath.empty, tpe, value, fields, ErrorRegistry.empty, userContext) handleScheme( - processFinalResolve( - resolveActionsPar(ExecutionPath.empty, tpe, actions, userContext, fields.namesOrdered)) - .map(_ -> userContext), - scheme) + effectScheme.map( + processFinalResolve( + resolveActionsPar(ExecutionPath.empty, tpe, actions, userContext, fields.namesOrdered)))( + _ -> userContext), + scheme + ) } def resolveFieldsSeq(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = { - val result = resolveSeq(ExecutionPath.empty, tpe, value, fields, ErrorRegistry.empty) + val result = resolveSeq(ExecutionPath.empty, tpe, value, fields) - handleScheme(result.flatMap(res => processFinalResolve(res._1).map(_ -> res._2)), scheme) + handleScheme( + effectScheme.flatMap(result)(res => + effectScheme.map(processFinalResolve(res._1))(_ -> res._2)), + scheme) } def resolveFieldsSubs(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( @@ -120,62 +126,68 @@ class Resolver[Ctx]( throw new IllegalStateException(s"Unsupported execution scheme: $s") } - def handleScheme( - result: Future[((Vector[RegisteredError], marshaller.Node), Ctx)], + private def handleScheme( + result: effectScheme.F[((Vector[RegisteredError], marshaller.Node), Ctx)], scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = scheme match { case ExecutionScheme.Default => - result.map { case ((_, res), _) => res }.asInstanceOf[scheme.Result[Ctx, marshaller.Node]] + effectScheme + .map(result) { case ((_, res), _) => res } + .asInstanceOf[scheme.Result[Ctx, marshaller.Node]] case ExecutionScheme.Extended => - result - .map { case ((errors, res), uc) => + effectScheme + .map(result) { case ((errors, res), uc) => ExecutionResult(uc, res, errors, middleware, validationTiming, queryReducerTiming) } .asInstanceOf[scheme.Result[Ctx, marshaller.Node]] case s: ExecutionScheme.StreamBasedExecutionScheme[_] => s.subscriptionStream - .singleFuture(result.map { + .singleFuture(effectScheme.toFuture(effectScheme.map(result) { case ((errors, res), uc) if s.extended => ExecutionResult(uc, res, errors, middleware, validationTiming, queryReducerTiming) case ((_, res), _) => res - }) + })) .asInstanceOf[scheme.Result[Ctx, marshaller.Node]] case s: EffectBasedExecutionScheme[_] => - s.mapEffect(result.map(_.swap)) { case (_, in) => in._2 } + effectScheme + .map(result) { case ((_, res), _) => res } .asInstanceOf[scheme.Result[Ctx, marshaller.Node]] case s => throw new IllegalStateException(s"Unsupported execution scheme: $s") } - def processFinalResolve(resolve: Resolve) = resolve match { - case Result(errors, data, _) => - Future.successful( - errors.originalErrors -> - marshalResult( - data.asInstanceOf[Option[resultResolver.marshaller.Node]], - marshalErrors(errors), - marshallExtensions.asInstanceOf[Option[resultResolver.marshaller.Node]], - beforeExecution = false - ).asInstanceOf[marshaller.Node]) - - case dr: DeferredResult => - immediatelyResolveDeferred( - userContext, - dr, - _.map { case (Result(errors, data, _)) => + private def processFinalResolve( + resolve: Resolve): effectScheme.F[(Vector[RegisteredError], marshaller.Node)] = + resolve match { + case Result(errors, data, _) => + effectScheme.success( errors.originalErrors -> marshalResult( data.asInstanceOf[Option[resultResolver.marshaller.Node]], marshalErrors(errors), marshallExtensions.asInstanceOf[Option[resultResolver.marshaller.Node]], beforeExecution = false - ).asInstanceOf[marshaller.Node] - } - ) - } + ).asInstanceOf[marshaller.Node]) + + case dr: DeferredResult => + effectScheme.fromFuture( + immediatelyResolveDeferred( + userContext, + dr, + _.map { case (Result(errors, data, _)) => + errors.originalErrors -> + marshalResult( + data.asInstanceOf[Option[resultResolver.marshaller.Node]], + marshalErrors(errors), + marshallExtensions.asInstanceOf[Option[resultResolver.marshaller.Node]], + beforeExecution = false + ).asInstanceOf[marshaller.Node] + } + )) + } private def marshallExtensions: Option[marshaller.Node] = { val extensions = @@ -201,16 +213,36 @@ class Resolver[Ctx]( res } - private def resolveDeferredWithGrouping(deferred: Vector[Future[Vector[Defer]]]) = + private def immediatelyResolveDeferredF[T]( + uc: Ctx, + dr: DeferredResult, + fn: effectScheme.F[Result] => effectScheme.F[T]): effectScheme.F[T] = { + val res = fn(effectScheme.fromFuture(dr.futureValue)) + + // TODO: we should something with result + val result = + effectScheme.map(resolveDeferredWithGroupingF(dr.deferred.map(effectScheme.fromFuture)))( + groups => groups.foreach(group => resolveDeferred(uc, group))) + + res + } + + private def resolveDeferredWithGrouping( + deferred: Vector[Future[Vector[Defer]]]): Future[Vector[Vector[Defer]]] = Future.sequence(deferred).map(listOfDef => deferredResolver.groupDeferred(listOfDef.flatten)) + private def resolveDeferredWithGroupingF( + deferred: Vector[effectScheme.F[Vector[Defer]]]): effectScheme.F[Vector[Vector[Defer]]] = + effectScheme.map(effectScheme.sequence(deferred))(listOfDef => + deferredResolver.groupDeferred(listOfDef.flatten)) + private type Actions = ( ErrorRegistry, Option[Vector[( Vector[ast.Field], Option[(Field[Ctx, _], Option[MappedCtxUpdate[Ctx, Any, Any]], LeafAction[Ctx, _])])]]) - def resolveSubs[S[_]]( + private def resolveSubs[S[_]]( path: ExecutionPath, tpe: ObjectType[Ctx, _], value: Any, @@ -259,15 +291,16 @@ class Resolver[Ctx]( val resMap = marshaller.emptyMapNode(Seq(origField.outputName)) Some( - marshallResult(Result( - updatedErrors, - Some( - marshaller.addMapNodeElem( - resMap.asInstanceOf[marshaller.MapBuilder], - fields.head.outputName, - marshaller.nullNode, - optional = isOptional(tpe, origField.name))) - ))) + marshallResult( + Result( + updatedErrors, + Some( + marshaller.addMapNodeElem( + resMap, + fields.head.outputName, + marshaller.nullNode, + optional = isOptional(tpe, origField.name))) + ))) case ErrorFieldResolution(updatedErrors) => Some(marshallResult(Result(updatedErrors, None))) case StreamFieldResolution(updatedErrors, svalue, standardFn) => @@ -311,61 +344,63 @@ class Resolver[Ctx]( } stream -> stream.mapFuture(stream.merge(fieldStreams.asInstanceOf[Vector[S[Result]]]))(r => - processFinalResolve(r.buildValue)) + effectScheme.toFuture(processFinalResolve(r.buildValue))) } - def resolveSeq( + private def resolveSeq( path: ExecutionPath, tpe: ObjectType[Ctx, _], value: Any, - fields: CollectedFields, - errorReg: ErrorRegistry): Future[(Result, Ctx)] = - fields.fields + fields: CollectedFields): effectScheme.F[(Result, Ctx)] = { + val result = fields.fields .foldLeft( - Future.successful( + effectScheme.success( ( Result(ErrorRegistry.empty, Some(marshaller.emptyMapNode(fields.namesOrdered))), userContext))) { case (future, elem) => - future.flatMap { resAndCtx => + effectScheme.flatMap(future) { resAndCtx => (resAndCtx, elem) match { - case (acc @ (Result(_, None, _), _), _) => Future.successful(acc) + case (acc @ (Result(_, None, _), _), _) => effectScheme.success(acc) case (acc, CollectedField(name, origField, _)) if tpe.getField(schema, origField.name).isEmpty => - Future.successful(acc) + effectScheme.success(acc) case ( (Result(errors, s @ Some(acc), _), uc), CollectedField(name, origField, Failure(error))) => - Future.successful(Result( - errors.add(path.add(origField, tpe), error), - if (isOptional(tpe, origField.name)) - Some( - marshaller.addMapNodeElem( - acc.asInstanceOf[marshaller.MapBuilder], - origField.outputName, - marshaller.nullNode, - optional = true)) - else None - ) -> uc) + effectScheme.success( + Result( + errors.add(path.add(origField, tpe), error), + if (isOptional(tpe, origField.name)) + Some( + marshaller.addMapNodeElem( + acc.asInstanceOf[marshaller.MapBuilder], + origField.outputName, + marshaller.nullNode, + optional = true)) + else None + ) -> uc) case ( (accRes @ Result(errors, s @ Some(acc), _), uc), CollectedField(name, origField, Success(fields))) => - resolveSingleFieldSeq( - path, - uc, - tpe, - value, - errors, - name, - origField, - fields, - accRes, - acc) + effectScheme.fromFuture( + resolveSingleFieldSeq( + path, + uc, + tpe, + value, + errors, + name, + origField, + fields, + accRes, + acc)) } } } - .map { case (res, ctx) => - res.buildValue -> ctx - } + effectScheme.map(result) { case (res, ctx) => + res.buildValue -> ctx + } + } private def resolveSingleFieldSeq( path: ExecutionPath, @@ -699,7 +734,7 @@ class Resolver[Ctx]( } } - def collectActionsPar( + private def collectActionsPar( path: ExecutionPath, tpe: ObjectType[Ctx, _], value: Any, @@ -782,7 +817,7 @@ class Resolver[Ctx]( "Subscription values are not supported for normal operations")))) } - def resolveActionsPar( + private def resolveActionsPar( path: ExecutionPath, tpe: ObjectType[Ctx, _], actions: Actions, @@ -1160,9 +1195,9 @@ class Resolver[Ctx]( } } - private def resolveDeferred(uc: Ctx, toResolve: Vector[Defer]) = + private def resolveDeferred(uc: Ctx, toResolve: Vector[Defer]): Unit = if (toResolve.nonEmpty) { - def findActualDeferred(deferred: Deferred[_]): Deferred[_] = deferred match { + @tailrec def findActualDeferred(deferred: Deferred[_]): Deferred[_] = deferred match { case MappingDeferred(d, _) => findActualDeferred(d) case d => d } @@ -1209,7 +1244,7 @@ class Resolver[Ctx]( } } - def resolveValue( + private def resolveValue( path: ExecutionPath, astFields: Vector[ast.Field], tpe: OutputType[_], @@ -1370,10 +1405,10 @@ class Resolver[Ctx]( } } - def isUndefinedValue(value: Any) = + private def isUndefinedValue(value: Any) = value == null || value == None - def resolveSimpleListValue( + private def resolveSimpleListValue( simpleRes: Iterable[Result], path: ExecutionPath, optional: Boolean, @@ -1704,7 +1739,7 @@ class Resolver[Ctx]( def isOptional(tpe: OutputType[_]): Boolean = tpe.isInstanceOf[OptionType[_]] - def nullForNotNullTypeError(position: Option[AstLocation]) = + private def nullForNotNullTypeError(position: Option[AstLocation]) = new ExecutionError( "Cannot return null for non-nullable type", exceptionHandler, @@ -1774,9 +1809,9 @@ class Resolver[Ctx]( optional = false) ) - def nodeValue = value.asInstanceOf[Option[marshaller.Node]] - def builderValue = value.asInstanceOf[Option[marshaller.MapBuilder]] - def buildValue = copy(value = builderValue.map(marshaller.mapNode)) + def nodeValue: Option[marshaller.Node] = value.asInstanceOf[Option[marshaller.Node]] + private def builderValue = value.asInstanceOf[Option[marshaller.MapBuilder]] + def buildValue: Result = copy(value = builderValue.map(marshaller.mapNode)) def appendErrors( path: ExecutionPath, @@ -1786,7 +1821,7 @@ class Resolver[Ctx]( else this } - case class ParentDeferredContext(uc: Ctx, expectedBranches: Int) { + private case class ParentDeferredContext(uc: Ctx, expectedBranches: Int) { val children = Vector.fill(expectedBranches)(ChildDeferredContext(Promise[Vector[Future[Vector[Defer]]]]())) @@ -1816,13 +1851,13 @@ class Resolver[Ctx]( } sealed trait FieldResolution - case class ErrorFieldResolution(errors: ErrorRegistry) extends FieldResolution - case class StandardFieldResolution( + private case class ErrorFieldResolution(errors: ErrorRegistry) extends FieldResolution + private case class StandardFieldResolution( errors: ErrorRegistry, action: LeafAction[Ctx, Any], ctxUpdate: Option[MappedCtxUpdate[Ctx, Any, Any]]) extends FieldResolution - case class StreamFieldResolution[Val, S[_]]( + private case class StreamFieldResolution[Val, S[_]]( errors: ErrorRegistry, value: SubscriptionValue[Ctx, Val, S], standardResolution: Any => StandardFieldResolution) diff --git a/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala b/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala index 569fd92c..8398b3b6 100644 --- a/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala +++ b/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala @@ -9,20 +9,35 @@ import sangria.macros._ import sangria.marshalling.circe._ import sangria.schema._ +import scala.collection.BuildFrom import scala.concurrent.{ExecutionContext, Future} /** The integration with [[cats.effect.IO]] is far from being complete for now. */ class IOExecutionScheme extends AnyWordSpec with Matchers { - private implicit val ec: ExecutionContext = global.compute + private implicit val ec: ExecutionContext = null private val ioEffectOps = new EffectOps[IO] { override def failed[Ctx, Res](error: Throwable): IO[Res] = IO.raiseError(error) override def flatMapFuture[Res, T](future: Future[T])(resultFn: T => IO[Res]): IO[Res] = IO.fromFuture(IO(future)).flatMap(resultFn) override def map[T, Out](in: Future[T])(f: T => Out): IO[Out] = IO.fromFuture(IO(in)).map(f) } + private val effectSchema = new EffectScheme { + override type F[A] = IO[A] + override def map[A, B](eff: IO[A])(f: A => B): IO[B] = eff.map(f) + override def flatMap[A, B](eff: IO[A])(f: A => IO[B]): IO[B] = eff.flatMap(f) + override def success[A](a: A): IO[A] = IO.pure(a) + override def sequence[A, CC[X] <: IterableOnce[X]](in: CC[IO[A]])(implicit + bf: BuildFrom[CC[IO[A]], A, CC[A]]): IO[CC[A]] = + in.iterator + .foldLeft(IO.pure(bf.newBuilder(in)))((accF, e) => accF.flatMap(acc => e.map(acc.addOne))) + .map(_.result()) + + override def fromFuture[A](f: Future[A]): IO[A] = IO.fromFuture(IO(f)) + override def toFuture[A](f: IO[A]): Future[A] = f.unsafeToFuture()(global) + } private implicit val ioExecutionScheme: EffectBasedExecutionScheme[IO] = - new EffectBasedExecutionScheme[IO](ioEffectOps) + new EffectBasedExecutionScheme[IO](ioEffectOps, effectSchema) import IOExecutionScheme._ "IOExecutionScheme" must {