diff --git a/modules/cats-effect-experimental/src/main/scala/sangria/catseffect/schema/AsyncValue.scala b/modules/cats-effect-experimental/src/main/scala/sangria/catseffect/schema/AsyncValue.scala index 83466ccc..ffc671d6 100644 --- a/modules/cats-effect-experimental/src/main/scala/sangria/catseffect/schema/AsyncValue.scala +++ b/modules/cats-effect-experimental/src/main/scala/sangria/catseffect/schema/AsyncValue.scala @@ -1,7 +1,7 @@ package sangria.catseffect.schema import cats.effect.Async -import sangria.schema.LeafAction +import sangria.schema.{Action, Context, LeafAction} import scala.concurrent.ExecutionContext import scala.language.implicitConversions @@ -12,6 +12,13 @@ case class AsyncValue[Ctx, Val, F[_]: Async](value: F[Val]) extends LeafAction[C new AsyncValue(Async[F].map(value)(fn)) } +object AsyncResolver { + implicit def asyncToAction[Ctx, Val, Res, F[_]: Async]( + resolver: Context[Ctx, Val] => F[Res]): Context[Ctx, Val] => Action[Ctx, Res] = { context => + AsyncValue(resolver(context)) + } +} + object AsyncValue { implicit def asyncAction[Ctx, Val, F[_]: Async](value: F[Val]): LeafAction[Ctx, Val] = AsyncValue(value) diff --git a/modules/cats-effect-experimental/src/test/scala/sangria/catseffect/IOExecutionSchemeSpec.scala b/modules/cats-effect-experimental/src/test/scala/sangria/catseffect/IOExecutionSchemeSpec.scala index 4024bc42..8c41fb8e 100644 --- a/modules/cats-effect-experimental/src/test/scala/sangria/catseffect/IOExecutionSchemeSpec.scala +++ b/modules/cats-effect-experimental/src/test/scala/sangria/catseffect/IOExecutionSchemeSpec.scala @@ -1,12 +1,13 @@ package sangria.catseffect import cats.effect.IO +import cats.effect.kernel.Async import cats.effect.unsafe.implicits.global import io.circe.Json import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.AnyWordSpec -import sangria.execution.Executor import sangria.catseffect.execution.IOExecutionScheme._ +import sangria.execution.Executor import sangria.macros._ import sangria.marshalling.circe._ import sangria.schema._ @@ -16,9 +17,11 @@ import sangria.schema._ class IOExecutionSchemeSpec extends AnyWordSpec with Matchers { import IOExecutionSchemeSpec._ + "IOExecutionScheme" must { "allow using IO effect with pure resolve" in { - val query = gql""" + val query = + gql""" query q1 { ids } @@ -56,14 +59,29 @@ class IOExecutionSchemeSpec extends AnyWordSpec with Matchers { } object IOExecutionSchemeSpec { - import sangria.catseffect.schema.AsyncValue._ + + import sangria.catseffect.schema.AsyncResolver._ + + private def resolve[F[_]: Async](): F[String] = { + import cats.syntax.functor._ + Async[F].pure(Option("hello")).map { + case Some(value) => value + case None => throw new Exception("No value") + } + } + private val QueryType: ObjectType[Unit, Unit] = ObjectType( "Query", () => fields[Unit, Unit]( Field("ids", ListType(IntType), resolve = _ => List(1, 2)), - Field("parent", StringType, resolve = _ => IO("hello")) - )) + Field.async( + "parent", + StringType, + resolve = _ => resolve[IO]() + ) + ) + ) private val schema = Schema(QueryType) } diff --git a/modules/core/src/main/scala/sangria/schema/Schema.scala b/modules/core/src/main/scala/sangria/schema/Schema.scala index e55923d3..82920dc8 100644 --- a/modules/core/src/main/scala/sangria/schema/Schema.scala +++ b/modules/core/src/main/scala/sangria/schema/Schema.scala @@ -1,22 +1,20 @@ package sangria.schema import sangria.ast.{AstNode, Document} - -import language.implicitConversions import sangria.execution.{FieldTag, SubscriptionField} +import sangria.introspection._ import sangria.marshalling.FromInput.{CoercedScalaResult, InputObjectResult} import sangria.marshalling._ -import sangria.{ast, introspection} -import sangria.validation._ -import sangria.introspection._ import sangria.renderer.{QueryRenderer, SchemaFilter, SchemaRenderer} import sangria.schema.InputObjectType.DefaultInput import sangria.streaming.SubscriptionStreamLike +import sangria.util.tag._ +import sangria.validation._ +import sangria.{ast, introspection} import scala.annotation.{implicitNotFound, tailrec} +import scala.language.implicitConversions import scala.reflect.ClassTag -import sangria.util.tag._ - import scala.util.matching.Regex sealed trait Type { @@ -655,6 +653,35 @@ object Field { astDirectives, Vector.empty) + def async[Ctx, Val, Res, Out, F[_]]( + name: String, + fieldType: OutputType[Out], + description: Option[String] = None, + arguments: List[Argument[_]] = Nil, + resolve: Context[Ctx, Val] => F[Res], + possibleTypes: => List[PossibleObject[_, _]] = Nil, + tags: List[FieldTag] = Nil, + complexity: Option[(Ctx, Args, Double) => Double] = None, + deprecationReason: Option[String] = None, + astDirectives: Vector[ast.Directive] = Vector.empty + )(implicit + ev: ValidOutType[Res, Out], + asyncToAction: (Context[Ctx, Val] => F[Res]) => Context[Ctx, Val] => Action[Ctx, Res]) + : Field[Ctx, Val] = + Field[Ctx, Val]( + name, + fieldType, + description, + arguments, + asyncToAction(resolve), + deprecationReason, + tags, + complexity, + () => possibleTypes.map(_.objectType), + astDirectives, + Vector.empty + ) + def subs[Ctx, Val, StreamSource, Res, Out]( name: String, fieldType: OutputType[Out],