Skip to content

Commit

Permalink
Add support to split the single client file into multiple files.
Browse files Browse the repository at this point in the history
Two new options have been added:
--splitFiles to split the client code generation into multiple files
--enableFmt  to disable formatting the generated files with scalafmt

The huge gitlab schema compiled in the scripted tests has been converted
to use these new options.
  • Loading branch information
alexdupre committed Jul 9, 2021
1 parent d1a70e3 commit 647b201
Show file tree
Hide file tree
Showing 11 changed files with 387 additions and 90 deletions.
30 changes: 24 additions & 6 deletions codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ sealed trait CalibanSettings {
def genView: Option[Boolean]
def scalarMappings: Seq[(String, String)]
def imports: Seq[String]
def splitFiles: Option[Boolean]
def enableFmt: Option[Boolean]

def append(other: Type): Type
}
Expand All @@ -24,7 +26,9 @@ case class CalibanFileSettings(
packageName: Option[String],
genView: Option[Boolean],
scalarMappings: Seq[(String, String)],
imports: Seq[String]
imports: Seq[String],
splitFiles: Option[Boolean],
enableFmt: Option[Boolean]
) extends CalibanSettings {
type Type = CalibanFileSettings
val headers = Seq.empty // Not applicable for file generator
Expand All @@ -37,7 +41,9 @@ case class CalibanFileSettings(
packageName = other.packageName.orElse(packageName),
genView = other.genView.orElse(genView),
scalarMappings = scalarMappings ++ other.scalarMappings,
imports = imports ++ other.imports
imports = imports ++ other.imports,
splitFiles = other.splitFiles.orElse(splitFiles),
enableFmt = other.enableFmt.orElse(enableFmt)
)

def clientName(value: String): CalibanFileSettings = this.copy(clientName = Some(value))
Expand All @@ -47,6 +53,8 @@ case class CalibanFileSettings(
def scalarMapping(mapping: (String, String)*): CalibanFileSettings =
this.copy(scalarMappings = this.scalarMappings ++ mapping)
def imports(values: String*): CalibanFileSettings = this.copy(imports = this.imports ++ values)
def splitFiles(value: Boolean): CalibanFileSettings = this.copy(splitFiles = Some(value))
def enableFmt(value: Boolean): CalibanFileSettings = this.copy(enableFmt = Some(value))
}

case class CalibanUrlSettings(
Expand All @@ -57,7 +65,9 @@ case class CalibanUrlSettings(
packageName: Option[String],
genView: Option[Boolean],
scalarMappings: Seq[(String, String)],
imports: Seq[String]
imports: Seq[String],
splitFiles: Option[Boolean],
enableFmt: Option[Boolean]
) extends CalibanSettings {
type Type = CalibanUrlSettings
def append(other: CalibanUrlSettings): CalibanUrlSettings =
Expand All @@ -69,7 +79,9 @@ case class CalibanUrlSettings(
packageName = other.packageName.orElse(packageName),
genView = other.genView.orElse(genView),
scalarMappings = scalarMappings ++ other.scalarMappings,
imports = imports ++ other.imports
imports = imports ++ other.imports,
splitFiles = other.splitFiles.orElse(splitFiles),
enableFmt = other.enableFmt.orElse(enableFmt)
)

def clientName(value: String): CalibanUrlSettings = this.copy(clientName = Some(value))
Expand All @@ -81,6 +93,8 @@ case class CalibanUrlSettings(
def scalarMapping(mapping: (String, String)*): CalibanUrlSettings =
this.copy(scalarMappings = this.scalarMappings ++ mapping)
def imports(values: String*): CalibanUrlSettings = this.copy(imports = this.imports ++ values)
def splitFiles(value: Boolean): CalibanUrlSettings = this.copy(splitFiles = Some(value))
def enableFmt(value: Boolean): CalibanUrlSettings = this.copy(enableFmt = Some(value))
}

object CalibanSettings {
Expand All @@ -92,7 +106,9 @@ object CalibanSettings {
packageName = Option.empty[String],
genView = Option.empty[Boolean],
scalarMappings = Seq.empty[(String, String)],
imports = Seq.empty[String]
imports = Seq.empty[String],
splitFiles = Option.empty[Boolean],
enableFmt = Option.empty[Boolean]
)

def emptyUrl(url: URL): CalibanUrlSettings = CalibanUrlSettings(
Expand All @@ -103,6 +119,8 @@ object CalibanSettings {
packageName = Option.empty[String],
genView = Option.empty[Boolean],
scalarMappings = Seq.empty[(String, String)],
imports = Seq.empty[String]
imports = Seq.empty[String],
splitFiles = Option.empty[Boolean],
enableFmt = Option.empty[Boolean]
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,15 @@ object CalibanSourceGenerator {
) // NB: Presuming zio-config can read toString'd booleans
val scalarMappings = pairList("--scalarMappings", settings.scalarMappings)
val imports = list("--imports", settings.imports)

scalafmtPath ++ headers ++ packageName ++ genView ++ scalarMappings ++ imports
val splitFiles = singleOpt(
"--splitFiles",
settings.splitFiles.map(_.toString())
) // NB: Presuming zio-config can read toString'd booleans
val enableFmt = singleOpt(
"--enableFmt",
settings.enableFmt.map(_.toString())
) // NB: Presuming zio-config can read toString'd booleans
scalafmtPath ++ headers ++ packageName ++ genView ++ scalarMappings ++ imports ++ splitFiles ++ enableFmt
}

def apply(
Expand Down
8 changes: 5 additions & 3 deletions codegen-sbt/src/sbt-test/codegen/test-compile/test
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ $ mkdir src/main/scala/genview/client
> calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/genview/client/ClientNameUniqueness.scala --packageName genview.client --genView true
$ exists src/main/scala/genview/client/ClientNameUniqueness.scala
$ exec sh verify.sh StarshipView ./src/main/scala/genview/client/ClientNameUniqueness.scala
> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true
$ exists src/main/scala/genview/client/ClientGitLab.scala
$ exec sh verify.sh VulnerableProjectsByGradeView ./src/main/scala/genview/client/ClientGitLab.scala
> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true --splitFiles true --enableFmt false
$ exists src/main/scala/genview/client/package.scala
$ exec sh verify.sh VulnerableProjectsByGrade ./src/main/scala/genview/client/package.scala
$ exists src/main/scala/genview/client/VulnerableProjectsByGrade.scala
$ exec sh verify.sh VulnerableProjectsByGradeView ./src/main/scala/genview/client/VulnerableProjectsByGrade.scala
> compile

$ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/caliban/Client.scala
Expand Down
10 changes: 8 additions & 2 deletions docs/docs/client.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@
def genView(value: Boolean): CalibanSettings // Provide a case class and helper method to select all fields on an object (default: false)
def scalarMapping(mapping: (String,String)*): CalibanSettings // A mapping from GraphQL scalar types to JVM types, as unknown scalar types are represented as String by default.
def imports(values: String*): CalibanSettings // A list of imports to be added to the top of a generated client
def splitFiles(value: Boolean): CalibanSettings // Split single client object into multiple files (default: false)
def enableFmt(value: Boolean): CalibanSettings // Enable code formatting with scalafmt (default: true)

// Only defined for `url` settings, for use in supplying extra headers when fetching the schema itself
def headers(pairs: (String,String)*): CalibanSettings
</code></pre></div><h3 id="calibangenclient"><a href="#calibangenclient" class="header-anchor">#</a> <code>calibanGenClient</code></h3> <p>If you prefer to generate the client explicitly rather than automatically, you can use <code>calibanGenClient</code> on the SBT CLI as follows:</p> <div class="language-scala extra-class"><pre class="language-scala"><code>calibanGenClient schemaPath outputPath <span class="token punctuation">[</span><span class="token operator">--</span>scalafmtPath path<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>headers name<span class="token operator">:</span>value<span class="token punctuation">,</span>name2<span class="token operator">:</span>value2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>genView <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>scalarMappings gqlType<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type<span class="token punctuation">,</span>gqlType2<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>imports a<span class="token punctuation">.</span>b<span class="token punctuation">.</span>c<span class="token punctuation">.</span>_<span class="token punctuation">,</span>c<span class="token punctuation">.</span>d<span class="token punctuation">.</span>E<span class="token punctuation">]</span>
</code></pre></div><h3 id="calibangenclient"><a href="#calibangenclient" class="header-anchor">#</a> <code>calibanGenClient</code></h3> <p>If you prefer to generate the client explicitly rather than automatically, you can use <code>calibanGenClient</code> on the SBT CLI as follows:</p> <div class="language-scala extra-class"><pre class="language-scala"><code>calibanGenClient schemaPath outputPath <span class="token punctuation">[</span><span class="token operator">--</span>scalafmtPath path<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>headers name<span class="token operator">:</span>value<span class="token punctuation">,</span>name2<span class="token operator">:</span>value2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>genView <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>scalarMappings gqlType<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type<span class="token punctuation">,</span>gqlType2<span class="token operator">:</span>f<span class="token punctuation">.</span>q<span class="token punctuation">.</span>d<span class="token punctuation">.</span>n<span class="token punctuation">.</span>Type2<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>imports a<span class="token punctuation">.</span>b<span class="token punctuation">.</span>c<span class="token punctuation">.</span>_<span class="token punctuation">,</span>c<span class="token punctuation">.</span>d<span class="token punctuation">.</span>E<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>splitFiles <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">--</span>enableFmt <span class="token boolean">true</span><span class="token operator">|</span><span class="token boolean">false</span><span class="token punctuation">]</span>

calibanGenClient project<span class="token operator">/</span>schema<span class="token punctuation">.</span>graphql src<span class="token operator">/</span>main<span class="token operator">/</span>client<span class="token operator">/</span>Client<span class="token punctuation">.</span>scala <span class="token operator">--</span>genView <span class="token boolean">true</span>
</code></pre></div><p>This command will generate a Scala file in <code>outputPath</code> containing helper functions for all the types defined in the provided GraphQL schema defined at <code>schemaPath</code>.
Expand All @@ -80,7 +82,11 @@
option.
Provide <code>--genView true</code> option if you want to generate a view for the GraphQL types.
If you want to force a mapping between a GraphQL type and a Scala class (such as scalars), you can use the
<code>--scalarMappings</code> option. Also you can add imports for example for your ArgEncoder implicits by providing <code>--imports</code> option.</p> <h2 id="query-building"><a href="#query-building" class="header-anchor">#</a> Query building</h2> <p>Once the boilerplate code is generated, you can start building queries. For each <em>type</em> in your schema, a corresponding Scala object has been created. For each <em>field</em> in your schema, a corresponding Scala function has been created.</p> <p>For example, given the following schema:</p> <div class="language-graphql extra-class"><pre class="language-graphql"><code><span class="token keyword">type</span> <span class="token class-name">Character</span> <span class="token punctuation">{</span>
<code>--scalarMappings</code> option. Also you can add imports for example for your ArgEncoder implicits by providing <code>--imports</code> option.
Use the <code>--splitFiles true</code> option if you want to generate multiple files within the same package instead of a single file.
In this case the filename part of the <code>outputPath</code> will be ignored, but the value will still be used to determine the mandatory package name and destination directory.
This can be helpful with large schemas and incremental compilation.
Provide <code>--enableFmt</code> option if you don't need to format generated files.</p> <h2 id="query-building"><a href="#query-building" class="header-anchor">#</a> Query building</h2> <p>Once the boilerplate code is generated, you can start building queries. For each <em>type</em> in your schema, a corresponding Scala object has been created. For each <em>field</em> in your schema, a corresponding Scala function has been created.</p> <p>For example, given the following schema:</p> <div class="language-graphql extra-class"><pre class="language-graphql"><code><span class="token keyword">type</span> <span class="token class-name">Character</span> <span class="token punctuation">{</span>
<span class="token attr-name">name</span><span class="token punctuation">:</span> <span class="token scalar">String</span><span class="token operator">!</span>
<span class="token attr-name">nicknames</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation">]</span><span class="token operator">!</span>
<span class="token attr-name">origin</span><span class="token punctuation">:</span> <span class="token class-name">Origin</span><span class="token operator">!</span>
Expand Down
Loading

0 comments on commit 647b201

Please sign in to comment.