Library Support
SCRAML allows to generate supporting types for a number of libraries:
cats
sourcelazy val root = (project in file("."))
.settings(
scalaVersion := "2.13.8",
name := "scraml-cats-test",
version := "0.1",
ramlFile := Some(file("api/simple.raml")),
librarySupport := Set(
scraml.libs.CatsEqSupport,
scraml.libs.CatsShowSupport,
scraml.libs.MonocleOpticsSupport
),
Compile / sourceGenerators += runScraml,
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.6.1",
"dev.optics" %% "monocle-core" % "3.0.0",
)
)
circe
sourceval circeVersion = "0.14.2"
lazy val root = (project in file("."))
.settings(
name := "scraml-json-test",
scalaVersion := "2.12.16",
version := "0.1",
ramlFile := Some(file("api/json.raml")),
basePackageName := "scraml",
librarySupport := Set(scraml.libs.CirceJsonSupport(formats = Map("localDateTime" -> "io.circe.Decoder.decodeLocalDateTime"))),
defaultEnumVariant := Some("Unknown"),
Compile / sourceGenerators += runScraml,
libraryDependencies += "com.commercetools" %% "sphere-json" % "0.12.5",
libraryDependencies ++= Seq(
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser"
).map(_ % circeVersion)
)
monocle
sourcelazy val root = (project in file("."))
.settings(
scalaVersion := "2.13.8",
name := "scraml-cats-test",
version := "0.1",
ramlFile := Some(file("api/simple.raml")),
librarySupport := Set(
scraml.libs.CatsEqSupport,
scraml.libs.CatsShowSupport,
scraml.libs.MonocleOpticsSupport
),
Compile / sourceGenerators += runScraml,
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.6.1",
"dev.optics" %% "monocle-core" % "3.0.0",
)
)
refined (with cats and circe)
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)
)
Sphere JSON
sourceval circeVersion = "0.14.1"
lazy val root = (project in file("."))
.settings(
scalaVersion := "2.12.16",
name := "scraml-ct-api-sphere-test",
version := "0.1",
ramlFile := Some(file("reference/api-specs/api/api.raml")),
basePackageName := "de.commercetools.api",
librarySupport := Set(scraml.libs.SphereJsonSupport),
Compile / sourceGenerators += runScraml,
libraryDependencies += "com.commercetools" %% "sphere-json" % "0.12.5"
)
tapir
the tapir support generating endpoint values:
Example tapir build.sbt
sourcescalaVersion := "2.13.14"
val circeVersion = "0.14.7"
val tapirVersion = "1.10.7"
lazy val examples = (project in file("."))
.settings(
name := "sbt-scraml-examples",
libraryDependencies ++= Seq(
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser"
).map(_ % circeVersion),
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-core" % tapirVersion,
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion,
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % tapirVersion,
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % tapirVersion,
libraryDependencies += "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % "3.9.6",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.4",
ramlFile := Some(file("../src/sbt-test/sbt-scraml/simple/api/simple.raml")),
ramlFieldMatchPolicy := scraml.FieldMatchPolicy.Exact(),
basePackageName := "scraml.examples",
librarySupport := Set(scraml.libs.CirceJsonSupport(), scraml.libs.TapirSupport("Endpoints")),
Compile / sourceGenerators += runScraml
)
Interpreting tapir endpoints
usage of the generated Endpoints.Greeting.getGreeting
type from the previous api example:
sourcepackage examples
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import cats.effect.{ExitCode, IO, IOApp}
import scraml.examples.{DataType, Endpoints, SomeEnum}
import scraml.examples.Endpoints.Greeting.GetGreetingParams
import sttp.client3.SttpBackend
import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend
import sttp.model.{Header, StatusCode}
import sttp.tapir.DecodeResult.{Failure, Value}
import sttp.tapir.client.sttp.WebSocketToPipe
import scala.concurrent.Future
class GreetingClient(apiUrl: String)(backend: SttpBackend[IO, Any])(implicit wsToPipe: WebSocketToPipe[Any]) {
import sttp.client3._
import sttp.tapir._
import sttp.tapir.client.sttp.SttpClientInterpreter
private lazy val client = SttpClientInterpreter()
private def authenticate: IO[String] = IO.pure("sometoken")
def getGreeting(params: GetGreetingParams): IO[DataType] = for {
accessToken <- authenticate
// adding an input and output to the endpoint to access headers and to provide an access token (checking not implemented)
response <- client.toClient(Endpoints.Greeting.getGreeting.in(auth.bearer[String]()).out(headers), Some(uri"$apiUrl"), backend)(wsToPipe)(params, accessToken)
result <- response match {
case Value(value) => IO.fromEither(value.left.map(error => new RuntimeException(s"error in $response: $error")))
case error: Failure => IO.raiseError(new RuntimeException(s"error while getting greeting: $error"))
}
(data, headers) = result
_ <- IO(println(s"got headers: $headers"))
} yield data
}
object GreetingClient {
def apply(apiUrl: String): IO[(GreetingClient, SttpBackend[IO, Any])] =
AsyncHttpClientCatsBackend[IO]().flatMap(backend => IO((new GreetingClient(apiUrl)(backend), backend)))
}
object GreetingServer {
import sttp.tapir._
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
import scala.concurrent.Future
import akka.http.scaladsl.server.Route
def getGreeting(params: GetGreetingParams): Future[Either[Unit, (DataType, StatusCode, List[Header])]] =
Future.successful(Right((DataType(params.name.getOrElse("no input"), customTypeProp = BigDecimal(42)), StatusCode.Ok, List(Header("custom-header", "value")))))
implicit val httpSystem: ActorSystem = ActorSystem("http")
import httpSystem.dispatcher
// adding outputs to provide statusCode and headers in the implementation
val greetingWithStatusAndHeaders = Endpoints.Greeting.getGreeting.out(statusCode and sttp.tapir.headers)
val greetingRoute: Route =
AkkaHttpServerInterpreter().toRoute(greetingWithStatusAndHeaders.serverLogic(getGreeting))
def startServer: IO[Http.ServerBinding] =
IO.fromFuture(IO(Http().newServerAt("localhost", 8080).bind(greetingRoute)))
}
object GreetingApp extends IOApp {
implicit class FutureOps[T](future: => Future[T]) {
def toIO: IO[T] = IO.fromFuture(IO(future))
}
override def run(args: List[String]): IO[ExitCode] = for {
binding <- GreetingServer.startServer
(clientWithBackend) <- GreetingClient(
apiUrl = s"http://${binding.localAddress.getHostName}:${binding.localAddress.getPort}"
)
(client, clientBackend) = clientWithBackend
result <- client.getGreeting(GetGreetingParams(enum_type = SomeEnum.A, name = Some("world"))).attempt
_ <- IO(println(result))
_ <-
clientBackend
.close()
.guarantee(binding.unbind().toIO.void)
.guarantee(GreetingServer.httpSystem.terminate().toIO.void)
} yield ExitCode.Success
}
The source code for this page can be found here.