Skip to content

Commit

Permalink
[Client] Allow query to specify every subtype of an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
AlixBa committed Nov 18, 2021
1 parent 58334d3 commit e84dfac
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 21 deletions.
94 changes: 73 additions & 21 deletions tools/src/main/scala/caliban/tools/ClientWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ object ClientWriter {
s"""$description${deprecated}def $safeName$typeParam$args$innerSelection$implicits: SelectionBuilder[$typeName, $outputType] = _root_.caliban.client.SelectionBuilder.Field("$name", $builder$argBuilder)"""
}

def collectFieldInfo(field: FieldDefinition, typeName: String, optionalUnion: Boolean): FieldInfo = {
def collectFieldInfo(
field: FieldDefinition,
typeName: String,
optionalUnion: Boolean,
optionalInterface: Boolean
): FieldInfo = {
val description = field.description match {
case Some(d) if d.trim.nonEmpty => s"/**\n * ${d.trim}\n */\n"
case _ => ""
Expand Down Expand Up @@ -188,15 +193,27 @@ object ClientWriter {
)
}
} else if (interfaceTypes.nonEmpty) {
(
s"[$typeLetter]",
s"(${interfaceTypes.map(t => s"""on${t.name}: Option[SelectionBuilder[${safeTypeName(t.name)}, $typeLetter]] = None""").mkString(", ")})",
writeType(field.ofType).replace(fieldType, typeLetter),
writeTypeBuilder(
field.ofType,
s"ChoiceOf(Map(${interfaceTypes.map(t => s""""${t.name}" -> on${t.name}""").mkString(", ")}).collect { case (k, Some(v)) => k -> Obj(v)})"
if (optionalInterface) {
(
s"[$typeLetter]",
s"(${interfaceTypes.map(t => s"""on${t.name}: Option[SelectionBuilder[${safeTypeName(t.name)}, $typeLetter]] = None""").mkString(", ")})",
s"Option[${writeType(field.ofType).replace(fieldType, typeLetter)}]",
writeTypeBuilder(
field.ofType,
s"ChoiceOf(Map(${interfaceTypes.map(t => s""""${t.name}" -> on${t.name}.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a)))""").mkString(", ")}))"
)
)
)
} else {
(
s"[$typeLetter]",
s"(${interfaceTypes.map(t => s"""on${t.name}: SelectionBuilder[${safeTypeName(t.name)}, $typeLetter]""").mkString(", ")})",
writeType(field.ofType).replace(fieldType, typeLetter),
writeTypeBuilder(
field.ofType,
s"ChoiceOf(Map(${interfaceTypes.map(t => s""""${t.name}" -> Obj(on${t.name})""").mkString(", ")}))"
)
)
}
} else {
(
s"[$typeLetter]",
Expand Down Expand Up @@ -224,7 +241,10 @@ object ClientWriter {
}.mkString(", ")})"
}

val name = if (optionalUnion && unionTypes.nonEmpty) safeName(field.name + "Option") else safeName(field.name)
val name =
if ((optionalUnion && unionTypes.nonEmpty) || (optionalInterface && interfaceTypes.nonEmpty))
safeName(field.name + "Option")
else safeName(field.name)
val owner = if (typeParam.nonEmpty) Some(fieldType) else None
val fieldTypeInfo = FieldTypeInfo(
field.name,
Expand Down Expand Up @@ -260,8 +280,13 @@ object ClientWriter {
case other => safeTypeName(other)
}

def writeField(field: FieldDefinition, typeName: String, optionalUnion: Boolean): String =
writeFieldInfo(collectFieldInfo(field, typeName, optionalUnion))
def writeField(
field: FieldDefinition,
typeName: String,
optionalUnion: Boolean,
optionalInterface: Boolean
): String =
writeFieldInfo(collectFieldInfo(field, typeName, optionalUnion, optionalInterface))

def reservedType(typeDefinition: ObjectTypeDefinition): Boolean =
typeDefinition.name == "Query" || typeDefinition.name == "Mutation" || typeDefinition.name == "Subscription"
Expand All @@ -274,7 +299,9 @@ object ClientWriter {
def writeRootQuery(typedef: ObjectTypeDefinition): String =
s"""object ${typedef.name} {
| ${typedef.fields
.map(writeField(_, "_root_.caliban.client.Operations.RootQuery", optionalUnion = false))
.map(
writeField(_, "_root_.caliban.client.Operations.RootQuery", optionalUnion = false, optionalInterface = false)
)
.mkString("\n ")}
|}
|""".stripMargin
Expand All @@ -287,7 +314,14 @@ object ClientWriter {
def writeRootMutation(typedef: ObjectTypeDefinition): String =
s"""object ${typedef.name} {
| ${typedef.fields
.map(writeField(_, "_root_.caliban.client.Operations.RootMutation", optionalUnion = false))
.map(
writeField(
_,
"_root_.caliban.client.Operations.RootMutation",
optionalUnion = false,
optionalInterface = false
)
)
.mkString("\n ")}
|}
|""".stripMargin
Expand All @@ -300,7 +334,14 @@ object ClientWriter {
def writeRootSubscription(typedef: ObjectTypeDefinition): String =
s"""object ${typedef.name} {
| ${typedef.fields
.map(writeField(_, "_root_.caliban.client.Operations.RootSubscription", optionalUnion = false))
.map(
writeField(
_,
"_root_.caliban.client.Operations.RootSubscription",
optionalUnion = false,
optionalInterface = false
)
)
.mkString("\n ")}
|}
|""".stripMargin
Expand All @@ -314,17 +355,28 @@ object ClientWriter {

def writeObject(typedef: ObjectTypeDefinition, genView: Boolean): String = {

val objectName: String = safeTypeName(typedef.name)
val objectName: String = safeTypeName(typedef.name)

val unionTypes = typesMap.collect { case (key, _: UnionTypeDefinition) => key }
val optionalUnionTypeFields = typedef.fields.flatMap { field =>
val isOptionalUnionType = unionTypes.exists(_.compareToIgnoreCase(field.ofType.toString) == 0)
if (isOptionalUnionType) Some(collectFieldInfo(field, objectName, optionalUnion = true))
if (isOptionalUnionType)
Some(collectFieldInfo(field, objectName, optionalUnion = true, optionalInterface = false))
else None
}
val fields = typedef.fields.map(collectFieldInfo(_, objectName, optionalUnion = false))
val view = if (genView) "\n " + writeView(typedef.name, fields.map(_.typeInfo)) else ""

val allFields = fields ++ optionalUnionTypeFields
val interfaceTypes = typesMap.collect { case (key, _: InterfaceTypeDefinition) => key }
val optionalInterfaceTypeFields = typedef.fields.flatMap { field =>
val isOptionalInterfaceType = interfaceTypes.exists(_.compareToIgnoreCase(field.ofType.toString) == 0)
if (isOptionalInterfaceType)
Some(collectFieldInfo(field, objectName, optionalUnion = false, optionalInterface = true))
else None
}

val fields = typedef.fields.map(collectFieldInfo(_, objectName, optionalUnion = false, optionalInterface = false))
val view = if (genView) "\n " + writeView(typedef.name, fields.map(_.typeInfo)) else ""

val allFields = fields ++ optionalUnionTypeFields ++ optionalInterfaceTypeFields

s"""object $objectName {$view
| ${allFields.map(writeFieldInfo).mkString("\n ")}
Expand Down Expand Up @@ -383,7 +435,7 @@ object ClientWriter {

case (field @ FieldTypeInfo(_, _, _, interfaceTypes, _, _, Some(_)), fieldName) if interfaceTypes.nonEmpty =>
val tpe = genericSelectionFieldTypesMap(field)
interfaceTypes.map(intType => s"${fieldName}On$intType: Option[SelectionBuilder[$intType, $tpe]] = None")
interfaceTypes.map(intType => s"${fieldName}On$intType: SelectionBuilder[$intType, $tpe]")
}.flatten

val viewClassFields: List[String] =
Expand Down
45 changes: 45 additions & 0 deletions tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,51 @@ object Client {
)
)
)
},
testM("interface") {
val schema =
"""
interface Order {
name: String!
}
type Ascending implements Order {
name: String!
}
type Sort {
order: Order
}
""".stripMargin

assertM(gen(schema, Map.empty, List.empty)) {
equalTo(
"""import caliban.client.FieldBuilder._
import caliban.client._
object Client {
type Ascending
object Ascending {
def name: SelectionBuilder[Ascending, String] = _root_.caliban.client.SelectionBuilder.Field("name", Scalar())
}
type Sort
object Sort {
def order[A](onAscending: SelectionBuilder[Ascending, A]): SelectionBuilder[Sort, Option[A]] =
_root_.caliban.client.SelectionBuilder.Field("order", OptionOf(ChoiceOf(Map("Ascending" -> Obj(onAscending)))))
def orderOption[A](
onAscending: Option[SelectionBuilder[Ascending, A]] = None
): SelectionBuilder[Sort, Option[Option[A]]] = _root_.caliban.client.SelectionBuilder.Field(
"order",
OptionOf(
ChoiceOf(Map("Ascending" -> onAscending.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a)))))
)
)
}
}
"""
)
}
}
) @@ TestAspect.sequential
}

0 comments on commit e84dfac

Please sign in to comment.