diff --git a/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala b/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala index 1b0dce9d9..d9a8c620c 100644 --- a/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala +++ b/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala @@ -22,9 +22,16 @@ trait SchemaDerivation[R] extends LowPriorityDerivedSchema { type Typeclass[T] = Schema[R, T] + def isValueType[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Boolean = + ctx.annotations.exists { + case GQLValueType() => true + case _ => false + } + def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Typeclass[T] = new Typeclass[T] { override def toType(isInput: Boolean, isSubscription: Boolean): __Type = - if (ctx.isValueClass && ctx.parameters.nonEmpty) ctx.parameters.head.typeclass.toType_(isInput, isSubscription) + if ((ctx.isValueClass || isValueType(ctx)) && ctx.parameters.nonEmpty) + ctx.parameters.head.typeclass.toType_(isInput, isSubscription) else if (isInput) makeInputObject( Some(ctx.annotations.collectFirst { case GQLInputName(suffix) => suffix } diff --git a/core/src/main/scala/caliban/schema/Annotations.scala b/core/src/main/scala/caliban/schema/Annotations.scala index 17e8108df..c0154b6ea 100644 --- a/core/src/main/scala/caliban/schema/Annotations.scala +++ b/core/src/main/scala/caliban/schema/Annotations.scala @@ -41,4 +41,9 @@ object Annotations { * Annotation to make a sealed trait a union instead of an enum */ case class GQLUnion() extends StaticAnnotation + + /** + * Annotation to make a union or interface redirect to a value type + */ + case class GQLValueType() extends StaticAnnotation } diff --git a/core/src/test/scala/caliban/schema/SchemaSpec.scala b/core/src/test/scala/caliban/schema/SchemaSpec.scala index 1405dc9b0..6ecba5fa6 100644 --- a/core/src/test/scala/caliban/schema/SchemaSpec.scala +++ b/core/src/test/scala/caliban/schema/SchemaSpec.scala @@ -1,8 +1,10 @@ package caliban.schema +import caliban.Rendering + import java.util.UUID import caliban.introspection.adt.{ __DeprecatedArgs, __Type, __TypeKind } -import caliban.schema.Annotations.{ GQLInterface, GQLUnion } +import caliban.schema.Annotations.{ GQLInterface, GQLUnion, GQLValueType } import zio.blocking.Blocking import zio.console.Console import zio.query.ZQuery @@ -126,6 +128,24 @@ object SchemaSpec extends DefaultRunnableSpec { implicit val somethingSchema: Schema[Any, Something] = Schema.gen[Something].rename("SomethingElse") assert(Types.innerType(introspectSubscription[Something]).name)(isSome(equalTo("SomethingElse"))) + }, + test("union redirect") { + case class Queries(union: RedirectingUnion) + + implicit val queriesSchema: Schema[Any, Queries] = Schema.gen[Queries] + + val types = Types.collectTypes(introspect[Queries]) + val subTypes = types.find(_.name.contains("RedirectingUnion")).flatMap(_.possibleTypes) + val fieldNames = + subTypes.toList.flatMap(_.flatMap(_.fields(__DeprecatedArgs()).map(_.map(_.name)))).toSet.flatten + assert(subTypes.map(_.flatMap(_.name)))( + isSome( + hasSameElements( + List("A", "B") + ) + ) + ) && + assert(fieldNames)(hasSameElements(List("common"))) } ) @@ -148,6 +168,18 @@ object SchemaSpec extends DefaultRunnableSpec { case object B extends EnumLikeUnion } + @GQLUnion + sealed trait RedirectingUnion + + object RedirectingUnion { + case class B(common: Int) + + case class A(common: Int) extends RedirectingUnion + + @GQLValueType + case class Redirect(value: B) extends RedirectingUnion + } + @GQLInterface sealed trait EnumLikeInterface object EnumLikeInterface {