# Code Generation

Caliban provides two sbt plugins to generate your client(s) code.

The first one, named CalibanPlugin, allows you to generate the client code from a schema file or from a server URL, manually or automatically.

The second one, named CompileTimeCalibanPlugin, allows you to generate the client code from your server code. This second "meta" plugin is actually made of two "concrete" plugins, CompileTimeCalibanServerPlugin and CompileTimeCalibanClientPlugin, that you'll both need to configure in your project to be able to generate you Caliban client code from your Caliban server code.

To use any of these two plugins, you'll first need to add following dependency to your project/plugins.sbt file:

addSbtPlugin("com.github.ghostdogpr" % "caliban-codegen-sbt" % "2.6.0")

# CalibanPlugin

The first step for building GraphQL queries with caliban-client is to generate boilerplate code from a GraphQL schema. For that, you need a file containing your schema (if your backend uses caliban, you can get it by calling GraphQL#render on your API).

And enable it in your build.sbt file:

enablePlugins(CalibanPlugin)

# From a schema file

At this point, the caliban command will cause any files in src/main/graphql to be translated into a Caliban-generated client library. This happens automatically any time you compile.

By default, all clients are generated with the same client name as the source file, in the caliban top-level package.

In order to supply more configuration options to the code generator, you can use the calibanSettings sbt setting, combined with the calibanSetting function to scope the settings to a particular file:

      // The `file("Service.graphql")` is a path suffix for some file in `src/main/graphql`
      Compile / caliban / calibanSettings += calibanSetting(file("Service.graphql"))(
        cs =>
          cs.packageName("com.example.graphql.client")
            .scalarMapping(
              "LanguageCode" -> "com.example.models.LanguageCode",
            )
            .scalarMapping(
              "Timestamp" -> "java.sql.Timestamp",
              "DayOfWeek" -> "java.time.DayOfWeek",
              "IntRange"  -> "com.github.tminglei.slickpg.Range[Int]"
            )
            .imports("com.example.graphql.client.implicits._")
      )

The path where the generator will look for schemas can be customized by overriding the calibanSources settings:

Compile / caliban / calibanSources := file("caliban")

If you want to cherry-pick certain files yourself, you can override that as well with an explicit caliban / sources entry:

Compile / caliban / sources := List(file("caliban") / "Service.graphql")

For every entry in calibanSettings for the same file, a separate client (or schema, depending on the entry's genType value) will be generated.

# From a server URL

The calibanSetting function also permits generating clients for supplied url's:

      Compile / caliban / calibanSettings += calibanSetting(url("http://my-example-service/graphql"))(
        cs =>
          cs.clientName("ExampleServiceClient")
            .packageName("com.example.graphql.client")
      )

# Generation settings

The settings available on the cs (CalibanSettings) builder are:

  • packageName: The package in which the code will be generated (default: caliban).
  • scalafmtPath: Path to a scalafmt config file (default: .scalafmt.conf).
  • genView: If true, will generate a case class and helper method to select all fields on an object (default: false).
  • scalarMappings: A mapping from GraphQL scalar types to JVM types, as unknown scalar types are represented as String by default.
  • imports: A list of imports to be added at the top of the generated code.
  • splitFiles: Whether to split the generated code into multiple files (default: false).
  • enableFmt: Enable code formatting with scalafmt (default: true).
  • extensibleEnums: Generate a fallback case class for unknown enum values (default: false).
  • headers (only defined for url settings): Supply extra headers when fetching the schema from a URL.
  • excludeDeprecated: Exclude fields and enum values with deprecated directive (default: false).

# Manual generation

If you prefer to generate the client explicitly rather than automatically, you can use calibanGenClient on the SBT CLI as follows:

calibanGenClient schemaPath outputPath [--scalafmtPath path] [--headers name:value,name2:value2] [--genView true|false] [--scalarMappings gqlType:f.q.d.n.Type,gqlType2:f.q.d.n.Type2] [--imports a.b.c._,c.d.E] [--splitFiles true|false] [--enableFmt true|false] [--extensibleEnums true|false] [--excludeDeprecated true|false]

# example
calibanGenClient project/schema.graphql src/main/client/Client.scala --genView true  

This command will generate a Scala file in outputPath containing helper functions for all the types defined in the provided GraphQL schema defined at schemaPath.

If you need to disable generating clients from src/main/graphql, include Compile / caliban / calibanGenerator := Seq.empty in your project settings.

The package of the generated code is derived from the folder of outputPath. This can be overridden by providing an alternative package with the --packageName option. Similarly, the generated object name is derived from outputPath file name but can be overridden with the --clientName option.

Other options are explained above.

# CompileTimeCalibanPlugin

As mentioned in the introduction of the Code Generation chapter, this "meta" plugin is actually made of two "concrete" sbt plugins, CompileTimeCalibanServerPlugin and CompileTimeCalibanClientPlugin, that you'll both need to configure in your project be able to generate your Caliban client code from your Caliban server code..

You can find a demo project using this plugin here: Demo project (opens new window)

To generate the Caliban client code from you Caliban server code, you need to do two things:

  1. Tell to the plugin where your Caliban GraphQL[R] instances for which you want to generate a client are and configure the client code generator.
    How to configure this is explained in the following Server side configuration chapter.

  2. Tell to the plugin where you want to generate your client(s).
    How to configure this is explained in the following Client side configuration chapter.

# Server side configuration

First, you'll need to activate the CompileTimeCalibanServerPlugin plugin in all the sbt modules of your project containing a GraphQL[R] instance for which you want to generate a client.

Let's say you have an api sbt module defined in your build.sbt which contains your Caliban server code:

lazy val api =
  project
    .enablePlugins(CompileTimeCalibanServerPlugin)

Now, you need to tell to the "server side" plugin where is your GraphQL[R] instance for which you want to generate a client.
This GraphQL[R] instance need to be public. If it's private or protected, the plugin code generator will not have access to it and will fail.

Let's say you have an object CalibanServer object in your api sbt module:

package com.example.my.awesome.project.api

import caliban._

object CalibanServer {
  
  val graphqlApi: GraphQL[MyEnv] = graphQL(Resolvers.resolver)
  
}

You'll need to add in your sbt definition:

lazy val api =
  project
    .enablePlugins(CompileTimeCalibanServerPlugin)
    .settings(
      Compile / ctCalibanServer / ctCalibanServerSettings :=
        Seq(
          "com.example.my.awesome.project.api.CalibanServer.graphqlApi" -> ClientGenerationSettings.default
        )
    )

This is the minimal working configuration for the "server side".

Now, you may want to tweak how the client code is generated.
For that, you'll have to replace the ClientGenerationSettings.default with the configuration that suits you the best.
This ClientGenerationSettings case class gives you the following configuration options:

  • packageName: The package in which the code will be generated (default: generated).
  • clientName: The name of the client class generated (default: Client).
  • scalafmtPath: Path to a scalafmt config file (default: .scalafmt.conf).
  • genView: If true, will generate a case class and helper method to select all fields on an object (default: false).
  • scalarMappings: A mapping from GraphQL scalar types to JVM types, as unknown scalar types are represented as String by default.
  • imports: A list of imports to be added at the top of the generated code.
  • splitFiles: Whether to split the generated code into multiple files (default: false).
  • enableFmt: Enable code formatting with scalafmt (default: true).
  • extensibleEnums: Generate a fallback case class for unknown enum values (default: false).
  • excludeDeprecated: Exclude fields or enum values with deprecated directive (default: false).

Let's take an example:

lazy val api =
  project
    .enablePlugins(CompileTimeCalibanServerPlugin)
    .settings(
      Compile / ctCalibanServer / ctCalibanServerSettings :=
        Seq(
          "com.example.my.awesome.project.api.CalibanServer.graphqlApi" ->
            ClientGenerationSettings(
              packageName = "com.example.my.awesome.project.client.generated",
              clientName = "CalibanClient",
              splitFiles = true
            ),
        )
    )

That's all. You now know how to configure the "server side" of this plugin.
Let's now see how to configure the "client side".

# Client side configuration

The "client side" of this plugin is here to help you define where your Caliban client code is generated.

The first thing to do is to activate the CompileTimeCalibanClientPlugin in the sbt module where you want your Caliban client code to be generated into.

Let's say you have a client sbt module defined in your build.sbt:

lazy val client =
  project
    .enablePlugins(CompileTimeCalibanClientPlugin)

You only have one thing left to do.
You need to reference your "server side" sbt module (here api) in your "client side" sbt module (here client) definition so the plugin knows that you want to generate the Caliban client code for your api server in this client sbt module:

lazy val client =
  project
    .enablePlugins(CompileTimeCalibanClientPlugin)
    .settings(
      Compile / ctCalibanClient / ctCalibanClientsSettings := Seq(api)
    )

This is the minimal working configuration for the "client side".

By default, the Caliban client code will be generated in your src/main/scala directory of your client sbt module.
You may prefer it not to be generated inside your - usually versioned-in-git - module code. For that, the plugin provides an option to generate the code in the target directory instead:

lazy val client =
  project
    .enablePlugins(CompileTimeCalibanClientPlugin)
    .settings(
      Compile / ctCalibanClient / ctCalibanClientsSettings := Seq(api),
      Compile / ctCalibanClient / ctCalibanClientsVersionedCode := false // By default, it's true.
    )

You're done. 🎉
You can now reload your sbt config and recompile your project. Your Caliban client code will be generated during the compilation process.

# Additional information about CompileTimeCalibanPlugin

As you may have seen in the demo project (opens new window), you can have more complex configurations for this plugin.
You can have more than one GraphQL[R] instance per server. Each GraphQL[R] instance can have its own client code generation configuration.
You can also have multiple "servers" referenced in your "client" module. The plugin will generate all the clients for all the "servers" referenced in your sbt definition.