Skip to content

Commit

Permalink
Allow union types to reuse the same member (#948)
Browse files Browse the repository at this point in the history
closes #826
  • Loading branch information
AlixBa authored Jul 6, 2021
1 parent d0ba2b8 commit 5496cec
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 13 deletions.
67 changes: 63 additions & 4 deletions tools/src/main/scala/caliban/tools/SchemaWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object SchemaWriter {
.map(union => (union, union.memberTypes.flatMap(schema.objectTypeDefinition)))
.toMap

val unions = unionTypes.map { case (union, objects) => writeUnion(union, objects) }.mkString("\n")
val unions = writeUnions(unionTypes)

val objects = schema.objectTypeDefinitions
.filterNot(obj =>
Expand Down Expand Up @@ -146,12 +146,71 @@ object SchemaWriter {
}
"""

def writeUnion(typedef: UnionTypeDefinition, objects: List[ObjectTypeDefinition])(implicit
def writeUnions(unions: Map[UnionTypeDefinition, List[ObjectTypeDefinition]])(implicit
scalarMappings: ScalarMappings
): String =
s"""${writeDescription(typedef.description)}sealed trait ${typedef.name} extends scala.Product with scala.Serializable
if (unions.nonEmpty) {
val flattened = unions.toList.flatMap { case (unionType, objectTypes) => objectTypes.map(_ -> unionType) }

val (unionsWithoutReusedMembers, reusedUnionMembers) = flattened
.foldLeft(
(
Map.empty[UnionTypeDefinition, List[ObjectTypeDefinition]],
Map.empty[ObjectTypeDefinition, List[UnionTypeDefinition]]
)
) {
case (
(unionsWithoutReusedMembers, reusedUnionMembers),
(objectType, unionType)
) =>
val isReused = reusedUnionMembers.contains(objectType) ||
flattened.exists { case (_objectType, _unionType) =>
_unionType.name != unionType.name && _objectType.name == objectType.name
}

if (isReused) {
(
unionsWithoutReusedMembers,
reusedUnionMembers.updated(
objectType,
reusedUnionMembers.getOrElse(objectType, List.empty) :+ unionType
)
)
} else {
(
unionsWithoutReusedMembers.updated(
unionType,
unionsWithoutReusedMembers.getOrElse(unionType, List.empty) :+ objectType
),
reusedUnionMembers
)
}
}

s"""${unions.keys.map(writeUnionSealedTrait).mkString("\n")}
${unionsWithoutReusedMembers.map { case (union, objects) => writeNotReusedMembers(union, objects) }
.mkString("\n")}
${reusedUnionMembers.map { case (objectType, unions) => writeReusedUnionMember(objectType, unions) }
.mkString("\n")}
"""
} else ""

def writeUnionSealedTrait(union: UnionTypeDefinition): String =
s"""${writeDescription(
union.description
)}sealed trait ${union.name} extends scala.Product with scala.Serializable"""

def writeReusedUnionMember(typedef: ObjectTypeDefinition, unions: List[UnionTypeDefinition])(implicit
scalarMappings: ScalarMappings
): String =
s"${writeObject(typedef)} extends ${unions.map(_.name).mkString(" with ")}"

object ${typedef.name} {
def writeNotReusedMembers(typedef: UnionTypeDefinition, objects: List[ObjectTypeDefinition])(implicit
scalarMappings: ScalarMappings
): String =
s"""object ${typedef.name} {
${objects
.map(o => s"${writeObject(o)} extends ${typedef.name}")
.mkString("\n")}
Expand Down
39 changes: 30 additions & 9 deletions tools/src/test/scala/caliban/tools/SchemaWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,19 @@ object SchemaWriterSpec extends DefaultRunnableSpec {
Captain or Pilot
\"\"\"
"""
val role2 =
s"""
\"\"\"
role2
Captain or Pilot or Stewart
\"\"\"
"""
val schema =
s"""
$role
union Role = Captain | Pilot
$role2
union Role2 = Captain | Pilot | Stewart
type Captain {
"ship" shipName: String!
Expand All @@ -233,28 +242,40 @@ object SchemaWriterSpec extends DefaultRunnableSpec {
type Pilot {
shipName: String!
}
type Stewart {
shipName: String!
}
""".stripMargin

assertM(gen(schema))(
equalTo {
val role =
val role =
s"""\"\"\"role
Captain or Pilot\"\"\""""
val role2 =
s"""\"\"\"role2
Captain or Pilot or Stewart\"\"\""""
s"""import caliban.schema.Annotations._

object Types {

@GQLDescription($role)
sealed trait Role extends scala.Product with scala.Serializable

object Role {
case class Captain(
@GQLDescription("ship")
shipName: String
) extends Role
case class Pilot(shipName: String) extends Role
sealed trait Role extends scala.Product with scala.Serializable
@GQLDescription($role2)
sealed trait Role2 extends scala.Product with scala.Serializable

object Role2 {
case class Stewart(shipName: String) extends Role2
}

case class Captain(
@GQLDescription("ship")
shipName: String
) extends Role
with Role2
case class Pilot(shipName: String) extends Role with Role2

}
"""
}
Expand Down

0 comments on commit 5496cec

Please sign in to comment.