Setup

Add the dependency to your build

project/plugins.sbt:

 addSbtPlugin("com.commercetools" % "sbt-scraml" % "0.16.0")

Configure your build

For projects which have one RAML API specification root document, each SCRAML setting can be specified individually, with ramlFile being the only required setting for runScraml.

build.sbt:

sourcelazy val root = (project in file("."))
  .settings(
    name := "scraml-simple-test",
    version := "0.1",
    ramlFile := Some(file("api/simple.raml")),
    ramlFieldMatchPolicy := scraml.FieldMatchPolicy.Exact(),
    Compile / sourceGenerators += runScraml
  )

Projects which have two or more RAML API specification root documents must use the ramlDefinitions setting and not ramlFile. If both are set, ramlFile will take precedence and ramlDefinitions will be ignored.

build.sbt:

sourcelazy val root = (project in file("."))
  .settings(
    scalaVersion := "2.13.8",
    name := "scraml-two-specifications",
    version := "0.1",
    librarySupport := Set(
      scraml.libs.CatsEqSupport,
      scraml.libs.CatsShowSupport,
      scraml.libs.MonocleOpticsSupport
    ),
    ramlDefinitions := Seq(
      scraml.ModelDefinition(
        raml = file("api/inline-types.raml"),
        basePackage = "scraml.inline",
        // Explicitly override the default Scala types for this definition.
        defaultTypes = scraml.DefaultTypes(
          float = "Double",
          number = "scala.math.BigDecimal"
        ),
        // Use DataTypes for RAML type definitions which do not have a
        // (package) annotation.
        defaultPackageAnnotation = Some("DataTypes"),
        // Explicitly override the global librarySupport setting.
        librarySupport = Set(scraml.libs.CatsShowSupport),
        formatConfig = None,
        generateDateCreated = true
        ),
      scraml.ModelDefinition(
        raml = file("api/simple.raml"),
        basePackage = "scraml.simple",
        defaultPackageAnnotation = None,
        formatConfig = None,
        generateDateCreated = true
        ),
      ),
    Compile / sourceGenerators += runScraml,
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-core" % "2.7.0",
      "dev.optics" %% "monocle-core" % "3.1.0"
    )
  )

Notes:

  • Each basePackage must be unique across all scraml.ModelDefinitions.
  • The defaultTypes can be set globally and will be used for each scraml.ModelDefinition which has a defaultTypes with all default values.
  • The libraryDefinitions can be set globally and will be used for each scraml.ModelDefinition which has an empty Set.

Tuning Code Generation

A project can tune what type of code is generated both by the LibrarySupport enabled as well as specifying a FieldMatchPolicy. The former is documented in the Library Support page and will not be covered here.

A FieldMatchPolicy determines whether or not additionalProperties are supported for a RAML type definition as well as what Circe will do in the presence of them (if Circe is enabled). There are four distinct policies available and a MatchInOrder policy which allows the specification of one or more FieldMatchPolicy instances applied in the order given.

To select a FieldMatchPolicy, set the project-wide ramlFieldMatchPolicy or specify the ramlFieldMatchPolicy Option in each scraml.ModelDefinition as desired. If a scraml.ModelDefinition does not specify a ramlFieldMatchPolicy, the project-wide ramlFieldMatchPolicy setting is used.

All policies which support additionalProperties for a given RAML Object Type have an additional case class constructor argument list generated of the form:

final case class Generated(...)(
  val additionalProperties: Option[Generated.AdditionalProperties] = None
)

Where Generated is the name of the Scala class created by scraml.

Types resulting in case class generation which do not have additionalProperties enabled will not have the additional constructor argument list. For example:

final case class NoAdditionalAllowed(...)

Note that traits and objects will not have additionalProperties generated for them.

Also note that use of the custom asMap annotation initiates an alternate code generation flow, bypassing the FieldMatchPolicy all together.

Default

This is the policy scraml will use if no other is specified. It adheres to the RAML Additional Properties specification as closely as possible by enabling additionalProperties support unless the additionalProperties facet is set to false for a RAML Object Type.

If Circe support is enabled, named properties will be used in the generated Decoder and any additional properties are kept in the additionalProperties property for RAML Object Types which have additionalProperties enabled. Additional properties will not cause the Decoder to fail for types which have additionalProperties disabled.

Note that Default will match all RAML Object Types. Since it is the default used and is the fallback policy for MatchInOrder, there is no need to explicitly configure this policy.

Exact

The Exact policy only generates additionalProperties support when one or more pattern properties are present in a RAML Object Type.

If Circe support is enabled, a derived Decoder is generated unless additionalProperties are allowed. Additional properties present for types which disallow additionalProperties will cause the Decoder to fail.

IgnoreExtra

The IgnoreExtra policy never generates additionalProperties support.

If Circe support is enabled, only named properties for a RAML Object Type will be used in the generated Decoder. Additional properties will not cause the Decoder to fail.

KeepExtra

The KeepExtra policy always generates additionalProperties support. This also implies there will be no singleton types generated as there could always be a possibility of additionalProperties being present on a per-instance basis.

If Circe support is enabled, named properties will be used in the generated Decoder. Additional properties will not cause the Decoder to fail.

MatchInOrder

The MatchInOrder policy is a model of the composite pattern and allows a project to specify in which order each policy will be applied. In order for policies specified after others to be considered, Exact, IgnoreExtra, and KeepExtra have an excluding: Set[String] constructor argument (defaulting to Set.empty). If a RAML Object Type has a displayName which matches a policy’s excluding configuration, that policy will not be applied and the next one will be attempted. If no policy matches, the Default policy is used.

build.sbt:

sourceimport scraml.FieldMatchPolicy._

val circeVersion = "0.14.1"
val refinedVersion = "0.9.27"

lazy val root = (project in file("."))
  .settings(
    scalaVersion := "2.13.8",
    name := "scraml-refined-test",
    version := "0.1",
    ramlFile := Some(file("api/refined.raml")),
    defaultTypes := scraml.DefaultTypes(long = "scala.math.BigInt"),
    // Override the default field match policy.
    ramlFieldMatchPolicy := MatchInOrder(
      // Generate exact field matching code except for these types.
      // Note that the exclusion set includes types which the next
      // policy is configured to exclude as well.  This allows them
      // to "fall through" to the last policy.
      Exact(
        excluding = Set(
          "ChildInheritsAll",
          "ChildOverridesAll",
          "DataType",
          "NoProps"
        )
      ) ::
      // Generate field matching code which ignores properties not
      // explicitly defined in the RAML *and* not matched above,
      // unless "excluding" matches.
      IgnoreExtra(
        excluding = Set(
          "DataType",
          "NoProps"
        )
      ) ::
      // If the above policies don't match, then try this one (which
      // will always match as its "excluding" Set is empty.
      KeepExtra() ::
      Nil
    ),
    librarySupport := Set(
      scraml.libs.CatsEqSupport,
      scraml.libs.CatsShowSupport,
      scraml.libs.CirceJsonSupport(
        formats = Map(
          "localDateTime" -> "io.circe.Decoder.decodeLocalDateTime"
        )
      ),
      scraml.libs.RefinedSupport
    ),
    Compile / sourceGenerators += runScraml,
    libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.7",
    libraryDependencies ++= Seq(
      "eu.timepit" %% "refined",
      "eu.timepit" %% "refined-cats"
    ).map(_ % refinedVersion),
    libraryDependencies ++= Seq(
        "io.circe" %% "circe-core",
        "io.circe" %% "circe-generic",
        "io.circe" %% "circe-parser",
        "io.circe" %% "circe-refined"
    ).map(_ % circeVersion)
  )
The source code for this page can be found here.