Skip to content

Commit

Permalink
feat(client): provide a way to select interfaces fields
Browse files Browse the repository at this point in the history
closes #237
  • Loading branch information
AlixBa committed Nov 25, 2021
1 parent 8727fce commit d66e93f
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 15 deletions.
44 changes: 44 additions & 0 deletions client/src/test/scala/caliban/client/SelectionBuilderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,50 @@ object SelectionBuilderSpec extends DefaultRunnableSpec {
assert(query.toGraphQL().query)(
equalTo("""query{addCharacter(character:{name:"name",nicknames:[]}){name}}""")
)
},
test("interface common fields and subtype fields combination") {
import caliban.client.FieldBuilder._
import caliban.client._

type Order = String
object Order {
def name: SelectionBuilder[Order, String] = _root_.caliban.client.SelectionBuilder.Field("name", Scalar())
}

type Ascending = String
object Ascending {
def name: SelectionBuilder[Ascending, String] =
_root_.caliban.client.SelectionBuilder.Field("name", Scalar())
}

type Sort = String
object Sort {
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))))
)
)
)
def orderInterface[A](order: SelectionBuilder[Order, A]): SelectionBuilder[Sort, Option[A]] =
_root_.caliban.client.SelectionBuilder.Field("order", OptionOf(Obj(order)))
}

object Query {
def sort[A](sel: SelectionBuilder[Sort, A]): Field[RootQuery, A] =
Field("sort", Obj(sel))
}

val selection = Sort.orderInterface(Order.name).withAlias("common") ~
Sort.orderOption(Some(Ascending.name)).withAlias("subtype")
assert(Query.sort(selection).toGraphQL().query)(
equalTo(
"query{sort{common:order{name} subtype:order{__typename ... on Ascending{name}}}}"
)
)
}
)
)
Expand Down
115 changes: 100 additions & 15 deletions tools/src/main/scala/caliban/tools/ClientWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ object ClientWriter {
field: FieldDefinition,
typeName: String,
optionalUnion: Boolean,
optionalInterface: Boolean
optionalInterface: Boolean,
commonInterface: Boolean
): FieldInfo = {
val description = field.description match {
case Some(d) if d.trim.nonEmpty => s"/**\n * ${d.trim}\n */\n"
Expand Down Expand Up @@ -193,7 +194,14 @@ object ClientWriter {
)
}
} else if (interfaceTypes.nonEmpty) {
if (optionalInterface) {
if (commonInterface) {
(
s"[$typeLetter]",
s"(${field.name}: SelectionBuilder[${safeTypeName(getTypeName(field.ofType))}, $typeLetter])",
writeType(field.ofType).replace(fieldType, typeLetter),
writeTypeBuilder(field.ofType, s"Obj(${field.name})")
)
} else if (optionalInterface) {
(
s"[$typeLetter]",
s"(${interfaceTypes.map(t => s"""on${t.name}: Option[SelectionBuilder[${safeTypeName(t.name)}, $typeLetter]] = None""").mkString(", ")})",
Expand Down Expand Up @@ -244,6 +252,8 @@ object ClientWriter {
val name =
if ((optionalUnion && unionTypes.nonEmpty) || (optionalInterface && interfaceTypes.nonEmpty))
safeName(field.name + "Option")
else if (commonInterface && interfaceTypes.nonEmpty)
safeName(field.name + "Interface")
else safeName(field.name)
val owner = if (typeParam.nonEmpty) Some(fieldType) else None
val fieldTypeInfo = FieldTypeInfo(
Expand Down Expand Up @@ -284,9 +294,10 @@ object ClientWriter {
field: FieldDefinition,
typeName: String,
optionalUnion: Boolean,
optionalInterface: Boolean
optionalInterface: Boolean,
commonInterface: Boolean
): String =
writeFieldInfo(collectFieldInfo(field, typeName, optionalUnion, optionalInterface))
writeFieldInfo(collectFieldInfo(field, typeName, optionalUnion, optionalInterface, commonInterface))

def reservedType(typeDefinition: ObjectTypeDefinition): Boolean =
typeDefinition.name == "Query" || typeDefinition.name == "Mutation" || typeDefinition.name == "Subscription"
Expand All @@ -300,7 +311,13 @@ object ClientWriter {
s"""object ${typedef.name} {
| ${typedef.fields
.map(
writeField(_, "_root_.caliban.client.Operations.RootQuery", optionalUnion = false, optionalInterface = false)
writeField(
_,
"_root_.caliban.client.Operations.RootQuery",
optionalUnion = false,
optionalInterface = false,
commonInterface = false
)
)
.mkString("\n ")}
|}
Expand All @@ -319,7 +336,8 @@ object ClientWriter {
_,
"_root_.caliban.client.Operations.RootMutation",
optionalUnion = false,
optionalInterface = false
optionalInterface = false,
commonInterface = false
)
)
.mkString("\n ")}
Expand All @@ -339,7 +357,8 @@ object ClientWriter {
_,
"_root_.caliban.client.Operations.RootSubscription",
optionalUnion = false,
optionalInterface = false
optionalInterface = false,
commonInterface = false
)
)
.mkString("\n ")}
Expand All @@ -361,19 +380,44 @@ object ClientWriter {
val optionalUnionTypeFields = typedef.fields.flatMap { field =>
val isOptionalUnionType = unionTypes.exists(_.compareToIgnoreCase(field.ofType.toString) == 0)
if (isOptionalUnionType)
Some(collectFieldInfo(field, objectName, optionalUnion = true, optionalInterface = false))
Some(
collectFieldInfo(
field,
objectName,
optionalUnion = true,
optionalInterface = false,
commonInterface = false
)
)
else None
}

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
Vector(
collectFieldInfo(
field,
objectName,
optionalUnion = false,
optionalInterface = true,
commonInterface = false
),
collectFieldInfo(
field,
objectName,
optionalUnion = false,
optionalInterface = false,
commonInterface = true
)
)
else Vector.empty
}

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

val allFields = fields ++ optionalUnionTypeFields ++ optionalInterfaceTypeFields
Expand Down Expand Up @@ -650,6 +694,43 @@ object ClientWriter {

val schemaDef = schema.schemaDefinition

val interfaceTypes =
if (splitFiles) schema.interfaceTypeDefinitions.map { typedef =>
writeObjectType(
ObjectTypeDefinition(
description = typedef.description,
name = typedef.name,
implements = List.empty,
directives = typedef.directives,
fields = typedef.fields
)
)
}
else Nil

val interfaces = schema.interfaceTypeDefinitions.map { typedef =>
val objDef = ObjectTypeDefinition(
description = typedef.description,
name = typedef.name,
implements = List.empty,
directives = typedef.directives,
fields = typedef.fields
)
val content = writeObject(objDef, genView)
val fullContent =
if (splitFiles)
s"""import caliban.client.FieldBuilder._
|import caliban.client._
|
|$content
|""".stripMargin
else
s"""${writeObjectType(objDef)}
|$content
|""".stripMargin
safeTypeName(typedef.name) -> fullContent
}

val objectTypes =
if (splitFiles)
schema.objectTypeDefinitions
Expand Down Expand Up @@ -807,11 +888,12 @@ object ClientWriter {
|$additionalImportsString
|
|package object ${packageName.get.reverse.takeWhile(_ != '.').reverse} {
| ${(scalars ::: objectTypes ::: queryTypes ::: mutationTypes ::: subscriptionTypes).mkString("\n")}
| ${(scalars ::: interfaceTypes ::: objectTypes ::: queryTypes ::: mutationTypes ::: subscriptionTypes)
.mkString("\n")}
|}
|""".stripMargin
val classFiles =
(enums ::: objects ::: inputs ::: queries.toList ::: mutations.toList ::: subscriptions.toList).map {
(enums ::: interfaces ::: objects ::: inputs ::: queries.toList ::: mutations.toList ::: subscriptions.toList).map {
case (name, content) =>
val fullContent =
s"""${packageName.fold("")(p => s"package $p\n\n")}
Expand All @@ -827,12 +909,14 @@ object ClientWriter {
s"""${if (enums.nonEmpty)
"""import caliban.client.CalibanClientError.DecodingError
|""".stripMargin
else ""}${if (objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty)
else ""}${if (
interfaces.nonEmpty || objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty
)
"""import caliban.client.FieldBuilder._
|""".stripMargin
else
""}${if (
enums.nonEmpty || objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty || inputs.nonEmpty
enums.nonEmpty || interfaces.nonEmpty || objects.nonEmpty || queries.nonEmpty || mutations.nonEmpty || subscriptions.nonEmpty || inputs.nonEmpty
)
"""import caliban.client._
|""".stripMargin
Expand All @@ -848,6 +932,7 @@ object ClientWriter {
|
| ${scalars.mkString("\n")}
| ${enums.map(_._2).mkString("\n")}
| ${interfaces.map(_._2).mkString("\n")}
| ${objects.map(_._2).mkString("\n")}
| ${inputs.map(_._2).mkString("\n")}
| ${queries.map(_._2).mkString("\n")}
Expand Down
7 changes: 7 additions & 0 deletions tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,11 @@ import caliban.client._
object Client {
type Order
object Order {
def name: SelectionBuilder[Order, String] = _root_.caliban.client.SelectionBuilder.Field("name", Scalar())
}
type Ascending
object Ascending {
def name: SelectionBuilder[Ascending, String] = _root_.caliban.client.SelectionBuilder.Field("name", Scalar())
Expand All @@ -882,6 +887,8 @@ object Client {
ChoiceOf(Map("Ascending" -> onAscending.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a)))))
)
)
def orderInterface[A](order: SelectionBuilder[Order, A]): SelectionBuilder[Sort, Option[A]] =
_root_.caliban.client.SelectionBuilder.Field("order", OptionOf(Obj(order)))
}
}
Expand Down

0 comments on commit d66e93f

Please sign in to comment.