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 for Abstract Effect Type in Schema Gen #951

Merged
merged 3 commits into from
Jul 7, 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
6 changes: 5 additions & 1 deletion codegen-sbt/src/main/scala/caliban/codegen/CalibanCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object CalibanCli {

private val genSchemaHelpMsg =
s"""
|calibanGenSchema schemaPath outputPath [--scalafmtPath path] [--headers name:value,name2:value2] [--packageName name] [--effect fqdn.Effect]
|calibanGenSchema schemaPath outputPath [--scalafmtPath path] [--headers name:value,name2:value2] [--packageName name] [--effect fqdn.Effect] [--abstractEffectType true|false]
|
|This command will create a Scala file in `outputPath` containing all the types
|defined in the provided GraphQL schema defined at `schemaPath`. Instead of a path,
Expand All @@ -61,6 +61,10 @@ object CalibanCli {
|
|By default, each Query and Mutation will be wrapped into a `zio.UIO` effect.
|This can be overridden by providing an alternative effect with the `--effect` option.
|The --abstractEffectType flag can also be used to indicate that the effect
|type is abstract, so that it will be added as a type parameter to the generated
|Query and Mutation classes (if applicable). In such cases `F` will be used by
|as the type parameter unless the `--effect` option is explicitly given.
|""".stripMargin

private val genClientHelpMsg =
Expand Down
21 changes: 13 additions & 8 deletions tools/src/main/scala/caliban/tools/Codegen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,23 @@ object Codegen {
arguments: Options,
genType: GenType
): Task[Unit] = {
val s = ".*/scala[^/]*/(.*)/(.*).scala".r.findFirstMatchIn(arguments.toPath)
val packageName = arguments.packageName.orElse(s.map(_.group(1).split("/").mkString(".")))
val objectName = s.map(_.group(2)).getOrElse("Client")
val effect = arguments.effect.getOrElse("zio.UIO")
val genView = arguments.genView.getOrElse(false)
val scalarMappings = arguments.scalarMappings
val loader = getSchemaLoader(arguments.schemaPath, arguments.headers)
val s = ".*/scala[^/]*/(.*)/(.*).scala".r.findFirstMatchIn(arguments.toPath)
val packageName = arguments.packageName.orElse(s.map(_.group(1).split("/").mkString(".")))
val objectName = s.map(_.group(2)).getOrElse("Client")
val abstractEffectType = arguments.abstractEffectType.getOrElse(false)
val effect = arguments.effect.getOrElse {
if (abstractEffectType) "F" else "zio.UIO"
}
val genView = arguments.genView.getOrElse(false)
val scalarMappings = arguments.scalarMappings
val loader = getSchemaLoader(arguments.schemaPath, arguments.headers)
for {
schema <- loader.load
code = genType match {
case GenType.Schema =>
SchemaWriter.write(schema, packageName, effect, arguments.imports)(ScalarMappings(scalarMappings))
SchemaWriter.write(schema, packageName, effect, arguments.imports, abstractEffectType)(
ScalarMappings(scalarMappings)
)
case GenType.Client =>
ClientWriter.write(schema, objectName, packageName, genView, arguments.imports)(
ScalarMappings(scalarMappings)
Expand Down
9 changes: 6 additions & 3 deletions tools/src/main/scala/caliban/tools/Options.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ final case class Options(
genView: Option[Boolean],
effect: Option[String],
scalarMappings: Option[Map[String, String]],
imports: Option[List[String]]
imports: Option[List[String]],
abstractEffectType: Option[Boolean]
)

object Options {
Expand All @@ -24,7 +25,8 @@ object Options {
genView: Option[Boolean],
effect: Option[String],
scalarMappings: Option[List[String]],
imports: Option[List[String]]
imports: Option[List[String]],
abstractEffectType: Option[Boolean]
)

def fromArgs(args: List[String]): Option[Options] =
Expand Down Expand Up @@ -62,7 +64,8 @@ object Options {
}
}.toMap
},
rawOpts.imports
rawOpts.imports,
rawOpts.abstractEffectType
)
}
case _ => None
Expand Down
17 changes: 10 additions & 7 deletions tools/src/main/scala/caliban/tools/SchemaWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ object SchemaWriter {
schema: Document,
packageName: Option[String] = None,
effect: String = "zio.UIO",
imports: Option[List[String]] = None
imports: Option[List[String]] = None,
isEffectTypeAbstract: Boolean = false
)(implicit scalarMappings: ScalarMappings): String = {
val schemaDef = schema.schemaDefinition

Expand Down Expand Up @@ -43,12 +44,12 @@ object SchemaWriter {

val queries = schema
.objectTypeDefinition(schemaDef.flatMap(_.query).getOrElse("Query"))
.map(t => writeRootQueryOrMutationDef(t, effect))
.map(t => writeRootQueryOrMutationDef(t, effect, isEffectTypeAbstract))
.getOrElse("")

val mutations = schema
.objectTypeDefinition(schemaDef.flatMap(_.mutation).getOrElse("Mutation"))
.map(t => writeRootQueryOrMutationDef(t, effect))
.map(t => writeRootQueryOrMutationDef(t, effect, isEffectTypeAbstract))
.getOrElse("")

val subscriptions = schema
Expand Down Expand Up @@ -103,17 +104,19 @@ object SchemaWriter {
s"${safeName(field.name)} :$argsTypeName $effect[${writeType(field.ofType)}]"
}

def writeRootQueryOrMutationDef(op: ObjectTypeDefinition, effect: String)(implicit
def writeRootQueryOrMutationDef(op: ObjectTypeDefinition, effect: String, isEffectTypeAbstract: Boolean)(implicit
scalarMappings: ScalarMappings
): String =
): String = {
val typeParamOrEmpty = if (isEffectTypeAbstract) s"[$effect[_]]" else ""
s"""
|${writeDescription(op.description)}case class ${op.name}(
|${writeDescription(op.description)}case class ${op.name}$typeParamOrEmpty(
|${op.fields.map(c => writeRootField(c, op, effect)).mkString(",\n")}
|)""".stripMargin

}
def writeSubscriptionField(field: FieldDefinition, od: ObjectTypeDefinition)(implicit
scalarMappings: ScalarMappings
): String =
): String =
Copy link
Contributor Author

@LaurenceWarne LaurenceWarne Jul 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sbt fmt chose to do this :D

"%s:%s ZStream[Any, Nothing, %s]".format(
safeName(field.name),
if (field.args.nonEmpty) s" ${argsName(field, od)} =>" else "",
Expand Down
34 changes: 32 additions & 2 deletions tools/src/test/scala/caliban/tools/OptionsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
None,
None,
None
)
)
Expand All @@ -44,6 +45,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
None,
None,
None
)
)
Expand All @@ -56,7 +58,7 @@ object OptionsSpec extends DefaultRunnableSpec {
assert(result)(
equalTo(
Some(
Options("schema", "output", None, None, None, None, None, None, None)
Options("schema", "output", None, None, None, None, None, None, None, None)
)
)
)
Expand Down Expand Up @@ -90,6 +92,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
None,
None,
None
)
)
Expand All @@ -111,6 +114,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
Some("cats.effect.IO"),
None,
None,
None
)
)
Expand All @@ -132,6 +136,7 @@ object OptionsSpec extends DefaultRunnableSpec {
Some(true),
None,
None,
None,
None
)
)
Expand All @@ -153,6 +158,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
Some(Map("Long" -> "scala.Long")),
None,
None
)
)
Expand All @@ -174,7 +180,30 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
None,
Some(List("a.b.Clazz", "b.c._"))
Some(List("a.b.Clazz", "b.c._")),
None
)
)
)
)
},
test("provide abstractEffectType") {
val input = List("schema", "output", "--effect", "F", "--abstractEffectType", "true")
val result = Options.fromArgs(input)
assert(result)(
equalTo(
Some(
Options(
"schema",
"output",
None,
None,
None,
None,
Some("F"),
None,
None,
Some(true)
)
)
)
Expand All @@ -195,6 +224,7 @@ object OptionsSpec extends DefaultRunnableSpec {
None,
None,
None,
None,
None
)
)
Expand Down
59 changes: 57 additions & 2 deletions tools/src/test/scala/caliban/tools/SchemaWriterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ object SchemaWriterSpec extends DefaultRunnableSpec {
val result = Parser
.parseQuery(schema)
.map(
_.objectTypeDefinition("Query").map(SchemaWriter.writeRootQueryOrMutationDef(_, "zio.UIO")).mkString("\n")
_.objectTypeDefinition("Query")
.map(SchemaWriter.writeRootQueryOrMutationDef(_, "zio.UIO", false))
.mkString("\n")
)
.flatMap(Formatter.format(_, None).map(_.trim))

Expand All @@ -100,7 +102,7 @@ object SchemaWriterSpec extends DefaultRunnableSpec {
.parseQuery(schema)
.map(
_.objectTypeDefinition("Mutation")
.map(SchemaWriter.writeRootQueryOrMutationDef(_, "zio.UIO"))
.map(SchemaWriter.writeRootQueryOrMutationDef(_, "zio.UIO", false))
.mkString("\n")
)
.flatMap(Formatter.format(_, None).map(_.trim))
Expand Down Expand Up @@ -134,6 +136,59 @@ object SchemaWriterSpec extends DefaultRunnableSpec {
)
)
},
testM("simple queries with abstracted effect type") {
val schema =
"""
type Query {
user(id: Int): User
userList: [User]!
}
type User {
id: Int
name: String
profilePic: String
}"""

val result = Parser
.parseQuery(schema)
.map(
_.objectTypeDefinition("Query").map(SchemaWriter.writeRootQueryOrMutationDef(_, "F", true)).mkString("\n")
)
.flatMap(Formatter.format(_, None).map(_.trim))

assertM(result)(
equalTo(
"""case class Query[F[_]](
user: QueryUserArgs => F[Option[User]],
userList: F[List[Option[User]]]
)""".stripMargin
)
)
},
testM("simple mutation with abstracted effect type") {
val schema =
"""
type Mutation {
setMessage(message: String): String
}
"""
val result = Parser
.parseQuery(schema)
.map(
_.objectTypeDefinition("Mutation")
.map(SchemaWriter.writeRootQueryOrMutationDef(_, "F", true))
.mkString("\n")
)
.flatMap(Formatter.format(_, None).map(_.trim))

assertM(result)(
equalTo(
"""case class Mutation[F[_]](
| setMessage: MutationSetMessageArgs => F[Option[String]]
|)""".stripMargin
)
)
},
testM("schema test") {
val schema =
"""
Expand Down
4 changes: 3 additions & 1 deletion vuepress/docs/docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ enablePlugins(CalibanPlugin)

Then call the `calibanGenSchema` sbt command.
```scala
calibanGenSchema schemaPath outputPath [--scalafmtPath path] [--headers name:value,name2:value2] [--packageName name] [--effect fqdn.Effect] [--scalarMappings gqlType:f.q.d.n.Type,gqlType2:f.q.d.n.Type2] [--imports a.b.c._,c.d.E]
calibanGenSchema schemaPath outputPath [--scalafmtPath path] [--headers name:value,name2:value2] [--packageName name] [--effect fqdn.Effect] [--scalarMappings gqlType:f.q.d.n.Type,gqlType2:f.q.d.n.Type2] [--imports a.b.c._,c.d.E] [--abstractEffectType true|false]

calibanGenSchema project/schema.graphql src/main/MyAPI.scala
```
Expand All @@ -246,6 +246,8 @@ The package of the generated code is derived from the folder of `outputPath`. Th

By default, each Query and Mutation will be wrapped into a `zio.UIO` effect. This can be overridden by providing an alternative effect with the `--effect` option.

You can also indicate that the effect type is abstract via `--abstractEffectType true`, in which case `Query` will be replaced by `Query[F[_]]` and so on (note `F` will be used unless `--effect <effect>` is explicitly given in which case `<effect>` would be used in place of `F`).

If you want to force a mapping between a GraphQL type and a Scala class (such as scalars), you can use the
`--scalarMappings` option. Also you can add additional imports by providing `--imports` option.

Expand Down