From 0d58e941f4763387d3a3bcd350bd05566c886dc7 Mon Sep 17 00:00:00 2001 From: Roman Belikin Date: Sun, 3 Aug 2025 19:53:05 +0400 Subject: [PATCH 1/3] Fix cats-effect ambiguity issue --- .../catseffect/schema/AsyncValue.scala | 9 +++++- .../catseffect/IOExecutionSchemeSpec.scala | 31 +++++++++++++++---- .../main/scala/sangria/schema/Schema.scala | 26 ++++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) 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..842c2f60 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..998f8844 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,24 +1,27 @@ 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._ /** The integration with [[cats.effect.IO]] is far from being complete for now. - */ + */ 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 } @@ -55,15 +58,31 @@ 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 502a414c..e7409cc2 100644 --- a/modules/core/src/main/scala/sangria/schema/Schema.scala +++ b/modules/core/src/main/scala/sangria/schema/Schema.scala @@ -655,6 +655,32 @@ 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], From d571c3afe76aa35017437afb429f50a23e222cc0 Mon Sep 17 00:00:00 2001 From: Roman Belikin Date: Sun, 3 Aug 2025 20:28:59 +0400 Subject: [PATCH 2/3] Format --- .../catseffect/schema/AsyncValue.scala | 2 +- .../main/scala/sangria/schema/Schema.scala | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) 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 842c2f60..38fc04eb 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 @@ -14,7 +14,7 @@ case class AsyncValue[Ctx, Val, F[_]: Async](value: F[Val]) extends LeafAction[C object AsyncResolver { - implicit def asyncToAction[Ctx, Val, Res, F[_]: Async](resolver: Context[Ctx, Val] => F[Res]): Context[Ctx, Val] => Action[Ctx, Res] = { + implicit def asyncToAction[Ctx, Val, Res, F[_] : Async](resolver: Context[Ctx, Val] => F[Res]): Context[Ctx, Val] => Action[Ctx, Res] = { context => AsyncValue(resolver(context)) } } diff --git a/modules/core/src/main/scala/sangria/schema/Schema.scala b/modules/core/src/main/scala/sangria/schema/Schema.scala index e4597f16..b484fb5b 100644 --- a/modules/core/src/main/scala/sangria/schema/Schema.scala +++ b/modules/core/src/main/scala/sangria/schema/Schema.scala @@ -656,18 +656,18 @@ object Field { 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] = + 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, From e362eb8fbe352686f63fb2f5e1e83cde274df323 Mon Sep 17 00:00:00 2001 From: Roman Belikin Date: Sun, 3 Aug 2025 20:38:55 +0400 Subject: [PATCH 3/3] Format --- .../catseffect/schema/AsyncValue.scala | 6 +-- .../catseffect/IOExecutionSchemeSpec.scala | 5 +-- .../main/scala/sangria/schema/Schema.scala | 41 ++++++++++--------- 3 files changed, 26 insertions(+), 26 deletions(-) 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 38fc04eb..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 @@ -12,10 +12,10 @@ 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)) + implicit def asyncToAction[Ctx, Val, Res, F[_]: Async]( + resolver: Context[Ctx, Val] => F[Res]): Context[Ctx, Val] => Action[Ctx, Res] = { context => + AsyncValue(resolver(context)) } } 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 998f8844..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 @@ -13,7 +13,7 @@ import sangria.marshalling.circe._ import sangria.schema._ /** The integration with [[cats.effect.IO]] is far from being complete for now. - */ + */ class IOExecutionSchemeSpec extends AnyWordSpec with Matchers { import IOExecutionSchemeSpec._ @@ -58,12 +58,11 @@ class IOExecutionSchemeSpec extends AnyWordSpec with Matchers { } } - object IOExecutionSchemeSpec { import sangria.catseffect.schema.AsyncResolver._ - private def resolve[F[_] : Async](): F[String] = { + private def resolve[F[_]: Async](): F[String] = { import cats.syntax.functor._ Async[F].pure(Option("hello")).map { case Some(value) => value diff --git a/modules/core/src/main/scala/sangria/schema/Schema.scala b/modules/core/src/main/scala/sangria/schema/Schema.scala index b484fb5b..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 { @@ -656,18 +654,20 @@ object Field { 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] = + 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, @@ -679,7 +679,8 @@ object Field { complexity, () => possibleTypes.map(_.objectType), astDirectives, - Vector.empty) + Vector.empty + ) def subs[Ctx, Val, StreamSource, Res, Out]( name: String,