diff --git a/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala index be5d5ae816..8866de900f 100644 --- a/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala @@ -46,7 +46,7 @@ trait CommonArgBuilderDerivation { def build(input: InputValue): Either[ExecutionError, T] = input match { case InputValue.ObjectValue(fields) => fromFields(fields) - case _ => Left(ExecutionError("expected an input object")) + case value => ctx.constructMonadic(p => p.typeclass.build(value)) } private[this] def fromFields(fields: Map[String, InputValue]): Either[ExecutionError, T] = diff --git a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala index 114867daec..e3b59b6bf7 100644 --- a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala @@ -1,6 +1,7 @@ package caliban.schema import caliban.CalibanError.ExecutionError +import caliban.InputValue.{ ListValue, VariableValue } import caliban.Value.* import caliban.schema.Annotations.{ GQLDefault, GQLName, GQLOneOfInput } import caliban.schema.macros.Macros @@ -140,24 +141,39 @@ trait CommonArgBuilderDerivation { def build(input: InputValue): Either[ExecutionError, A] = input match { case InputValue.ObjectValue(fields) => fromFields(fields) - case _ => Left(ExecutionError("expected an input object")) + case value => fromValue(value) } private def fromFields(fields: Map[String, InputValue]): Either[ExecutionError, A] = { - var i = 0 - val l = params.length - var acc: Tuple = EmptyTuple + var i = 0 + val l = params.length + val arr = Array.ofDim[Any](l) while (i < l) { val (label, default, builder) = params(i) val field = fields.getOrElse(label, null) val value = if (field ne null) builder.build(field) else default value match { - case Right(v) => acc :*= v - case e @ Left(_) => return e.asInstanceOf[Either[ExecutionError, A]] + case Right(v) => arr(i) = v + case Left(e) => return Left(e) } i += 1 } - Right(fromProduct(acc)) + Right(fromProduct(Tuple.fromArray(arr))) + } + + private def fromValue(input: InputValue): Either[ExecutionError, A] = { + val l = params.length + val arr = Array.ofDim[Any](l) + var i = 0 + while (i < l) { + val (_, _, builder) = params(i) + builder.build(input) match { + case Right(v) => arr(i) = v + case Left(e) => return Left(e) + } + i += 1 + } + Right(fromProduct(Tuple.fromArray(arr))) } } diff --git a/core/src/test/scala-2/caliban/schema/ArgBuilderScala2Spec.scala b/core/src/test/scala-2/caliban/schema/ArgBuilderScala2Spec.scala new file mode 100644 index 0000000000..676dcaadfe --- /dev/null +++ b/core/src/test/scala-2/caliban/schema/ArgBuilderScala2Spec.scala @@ -0,0 +1,27 @@ +package caliban.schema + +import caliban.Value.StringValue +import caliban.schema.ArgBuilder.auto._ +import zio.test.Assertion._ +import zio.test._ + +import java.util.UUID + +object ArgBuilderScala2Spec extends ZIOSpecDefault { + def spec = suite("ArgBuilderScala2")( + suite("AnyVal") { + test("ArgBuilder that extends AnyVal") { + val id = UUID.randomUUID() + val value = ArgBuilder[UUIDId].build(StringValue(id.toString)) + assert(value)(isRight(equalTo(UUIDId(id)))) + } + } + ) + + trait Ids[T] extends Any { + self: AnyVal => + def value: T + } + + final case class UUIDId(value: UUID) extends AnyVal with Ids[UUID] +} diff --git a/core/src/test/scala/caliban/execution/ExecutionSpec.scala b/core/src/test/scala/caliban/execution/ExecutionSpec.scala index 8976106c63..c31004369d 100644 --- a/core/src/test/scala/caliban/execution/ExecutionSpec.scala +++ b/core/src/test/scala/caliban/execution/ExecutionSpec.scala @@ -860,6 +860,27 @@ object ExecutionSpec extends ZIOSpecDefault { .flatMap(_.execute(query)) .map(result => assertTrue(result.data.toString == """null""")) }, + test("using value types as inputs") { + @GQLValueType + case class UserArgs(id: Int) + case class User(test: UserArgs => String) + case class Mutations(user: UIO[User]) + case class Queries(a: Int) + val api = graphQL(RootResolver(Queries(1), Mutations(ZIO.succeed(User(_.toString))))) + + val interpreter = api.interpreter + val query = + """mutation { + | user { + | test(value: 123) + | } + |}""".stripMargin + interpreter + .flatMap(_.execute(query)) + .debug + .map(_.data.toString) + .map(result => assertTrue(result == """{"user":{"test":"UserArgs(123)"}}""")) + }, test("die inside a nullable list") { case class Queries(test: List[Task[String]]) val api = graphQL(RootResolver(Queries(List(ZIO.succeed("a"), ZIO.die(new Exception("Boom"))))))