Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow union types to reuse the same member #948

Merged
merged 1 commit into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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