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 allscraml.ModelDefinition
s. - The
defaultTypes
can be set globally and will be used for eachscraml.ModelDefinition
which has adefaultTypes
with all default values. - The
libraryDefinitions
can be set globally and will be used for eachscraml.ModelDefinition
which has an emptySet
.
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 trait
s and object
s 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)
)