Skip to content

Commit

Permalink
Add Client / Schema codegen for oneOf inputs (#2294)
Browse files Browse the repository at this point in the history
* Add client / schema codegen for oneOf inputs

* Add test for codegen

* Another test
  • Loading branch information
kyri-petrou authored Jun 25, 2024
1 parent 7d0a698 commit 95c9f2d
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
directive @oneOf on INPUT_OBJECT

# Schema
schema {
query: Q
Expand All @@ -9,6 +11,8 @@ type Q {
characters: [Character!]!
# nested object type with argument
character(name: String!): Character
# oneOf input object
character2(oneOfThese: CharacterOneOfInput!): Character

# default arguments for optional and list arguments
characters2(
Expand Down Expand Up @@ -36,6 +40,17 @@ input CharacterInput {
wait: String!
}

# Input object oneOf
input CharacterOneOfInput @oneOf {
name: String
nickname: String
origin: Origin

# reserved name
wait: String
}


# Object
type Character {
name: String!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
directive @lazy on FIELD_DEFINITION
directive @oneOf on INPUT_OBJECT

# Schema
schema {
Expand All @@ -11,6 +12,8 @@ type Q {
characters: [Character!]!
# nested object type with argument
character(name: String!): Character
# oneOf input object
character2(oneOfThese: CharacterOneOfInput!): Character

# default arguments for optional and list arguments
characters2(
Expand Down Expand Up @@ -41,6 +44,16 @@ input CharacterInput {
wait: String!
}

# Input object
input CharacterOneOfInput @oneOf {
name: String
nickname: String
origin: Origin

# reserved name
wait: String
}

# Object
type Character {
name: String!
Expand All @@ -63,12 +76,12 @@ type Character {
# Deprecated field + comment with double quotes and newlines
"""a deprecated field"""
oldName3: String!
@deprecated(reason: """
This field is deprecated for the following reasons:
1. "Outdated data model": The field relies on an outdated data model.
2. "Performance issues": Queries using this field have performance issues.
Please use `name` instead.
""")
@deprecated(reason: """
This field is deprecated for the following reasons:
1. "Outdated data model": The field relies on an outdated data model.
2. "Performance issues": Queries using this field have performance issues.
Please use `name` instead.
""")
}

# Enum
Expand Down Expand Up @@ -110,14 +123,14 @@ type Product {
}

type Canterbury {
officer: Officer!
officer: Officer!
}

type Officer {
dossier: Dossier! @lazy
dossier: Dossier! @lazy
}

type Dossier {
homeWorld: String!
faction: String! @lazy
homeWorld: String!
faction: String! @lazy
}
1 change: 1 addition & 0 deletions codegen-sbt/src/sbt-test/codegen/test-compile/test
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ $ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.s
$ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/caliban/schema.scala
$ exec sh verify.sh CharacterView target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.scala
$ exec sh verify.sh OptionView target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.scala
$ exec sh verify.sh CharacterOneOfInput target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.scala
8 changes: 7 additions & 1 deletion core/src/main/scala/caliban/parsing/adt/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ sealed trait Type extends Serializable { self =>
case Type.ListType(ofType, nonNull) => if (nonNull) s"[$ofType]!" else s"[$ofType]"
}

def toNullable: Type =
final def toNullable: Type =
self match {
case Type.NamedType(name, _) => Type.NamedType(name, nonNull = false)
case Type.ListType(ofType, _) => Type.ListType(ofType, nonNull = false)
}

final def toNonNullable: Type =
self match {
case Type.NamedType(name, _) => Type.NamedType(name, nonNull = true)
case Type.ListType(ofType, _) => Type.ListType(ofType, nonNull = true)
}
}

object Type {
Expand Down
34 changes: 32 additions & 2 deletions tools/src/main/scala/caliban/tools/ClientWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import caliban.Value.StringValue
import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition
import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition._
import caliban.parsing.adt.Type.{ ListType, NamedType }
import caliban.parsing.adt.{ Document, Type }
import caliban.parsing.adt.{ Directives, Document, Type }

import scala.annotation.tailrec

Expand Down Expand Up @@ -654,6 +654,34 @@ object ClientWriter {
|}""".stripMargin
}

def writeOneOfInputObject(
typedef: InputObjectTypeDefinition
): String = {
val inputObjectName = safeTypeName(typedef.name)

val leafTypes = typedef.fields.map { f =>
val name = f.name
val tpe = f.ofType.toNonNullable
val fNonNull = f.copy(ofType = tpe)
val inputValue = writeInputValue(tpe, s"${safeName(f.name)}", inputObjectName)

s"""final case class ${name.capitalize}(${writeArgumentFields(List(fNonNull))}) extends $inputObjectName {
protected def encode: __Value = __ObjectValue(List("${f.name}" -> $inputValue))
}""".stripMargin
}.mkString("\n")

s"""sealed trait $inputObjectName {
| protected def encode: __Value
|}
|object $inputObjectName {
| $leafTypes
|
| implicit val encoder: ArgEncoder[$inputObjectName] = new ArgEncoder[$inputObjectName] {
| override def encode(value: $inputObjectName): __Value = value.encode
| }
|}""".stripMargin
}

def writeInputValue(
t: Type,
fieldName: String,
Expand Down Expand Up @@ -849,7 +877,9 @@ object ClientWriter {
}

val inputs = schema.inputObjectTypeDefinitions.map { typedef =>
val content = writeInputObject(typedef)
val content =
if (Directives.isOneOf(typedef.directives)) writeOneOfInputObject(typedef)
else writeInputObject(typedef)
val fullContent =
if (splitFiles)
s"""import caliban.client._
Expand Down
25 changes: 24 additions & 1 deletion tools/src/main/scala/caliban/tools/SchemaWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,26 @@ object SchemaWriter {
.mkString(", ")})$derivesSchemaAndArgBuilder"""
}

def writeOneOfInputObject(typedef: InputObjectTypeDefinition): String = {
val name = typedef.name
val maybeAnnotation = if (preserveInputNames) s"""@GQLInputName("$name")\n""" else ""
s"""
$maybeAnnotation${writeTypeAnnotations(
typedef
)}@GQLOneOfInput\nsealed trait $name extends scala.Product with scala.Serializable
object $name {
${typedef.fields.map(writeOneOfInputField(_, name)).mkString("\n")}
}
"""
}

def writeOneOfInputField(typedef: InputValueDefinition, parent: String): String = {
val name = typedef.name.capitalize
s"""${writeInputAnnotations(typedef)}final case class $name(${writeInputValue(
typedef.copy(ofType = typedef.ofType.toNonNullable)
)}) extends $parent$derivesSchemaAndArgBuilder"""
}

def writeEnum(typedef: EnumTypeDefinition): String =
s"""${writeTypeAnnotations(
typedef
Expand Down Expand Up @@ -417,7 +437,10 @@ object SchemaWriter {
}
.mkString("\n")

val inputs = schema.inputObjectTypeDefinitions.map(writeInputObject).mkString("\n")
val inputs = schema.inputObjectTypeDefinitions.map { typedef =>
if (Directives.isOneOf(typedef.directives)) writeOneOfInputObject(typedef)
else writeInputObject(typedef)
}.mkString("\n")

val enums = schema.enumTypeDefinitions.map(writeEnum).mkString("\n")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import caliban.client._
import caliban.client.__Value._

object Client {

sealed trait CharacterInput {
protected def encode: __Value
}
object CharacterInput {
final case class Name(name: String) extends CharacterInput {
protected def encode: __Value = __ObjectValue(List("name" -> implicitly[ArgEncoder[String]].encode(name)))
}
final case class Nicknames(nicknames: List[String] = Nil) extends CharacterInput {
protected def encode: __Value = __ObjectValue(
List("nicknames" -> __ListValue(nicknames.map(value => implicitly[ArgEncoder[String]].encode(value))))
)
}

implicit val encoder: ArgEncoder[CharacterInput] = new ArgEncoder[CharacterInput] {
override def encode(value: CharacterInput): __Value = value.encode
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import caliban.schema.Annotations._

object Types {

final case class Character(name: String)

@GQLOneOfInput
sealed trait CharacterArgs extends scala.Product with scala.Serializable
object CharacterArgs {
final case class Foo(foo: String) extends CharacterArgs
final case class Bar(bar: Int) extends CharacterArgs
}

}
10 changes: 9 additions & 1 deletion tools/src/test/scala/caliban/tools/ClientWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ object ClientWriterSpec extends SnapshotTest {
}
""")
},
snapshotTest("input object oneOf") {
gen("""
input CharacterInput @oneOf {
name: String
nicknames: [String!]
}
""")
},
snapshotTest("input object with reserved name") {
gen("""
input CharacterInput {
Expand Down Expand Up @@ -413,5 +421,5 @@ object ClientWriterSpec extends SnapshotTest {
}
""")
}
) @@ TestAspect.sequential
) @@ TestAspect.parallelN(4)
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,5 @@ object ClientWriterViewSpec extends SnapshotTest {
}
""")
}
) @@ TestAspect.sequential
) @@ TestAspect.parallelN(4)
}
10 changes: 10 additions & 0 deletions tools/src/test/scala/caliban/tools/SchemaWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ object SchemaWriterSpec extends SnapshotTest {
name: String!
}
""")),
snapshotTest("input type oneOf")(gen("""
type Character {
name: String!
}
input CharacterArgs @oneOf {
foo: String
bar: Int
}
""")),
snapshotTest("input type with preserved input")(
gen(
"""
Expand Down

0 comments on commit 95c9f2d

Please sign in to comment.