Skip to main content

SimpleRestJson server

The smithy4s-http4s module provides functions that transform instances of the generated interfaces into http4s routes, provided the corresponding service definitions (in smithy) are annotated with the alloy#simpleRestJson protocol.

In build.sbt

libraryDependencies ++= Seq(
// version sourced from the plugin
"com.disneystreaming.smithy4s" %% "smithy4s-http4s" % smithy4sVersion.value
)

In MyHelloWorld.scala, implement the service interface that is generated at build-time:

// the package under which the scala code was generated
import smithy4s.example.hello._

import cats.effect.IO

object HelloWorldImpl extends HelloWorldService[IO] {

def hello(name: String, town: Option[String]): IO[Greeting] = IO.pure {
town match {
case None => Greeting(s"Hello $name !")
case Some(t) => Greeting(s"Hello $name from $t !")
}
}

}

In Routes.scala

import smithy4s.http4s.SimpleRestJsonBuilder
import org.http4s._
import cats.effect.IO
import cats.effect.Resource

object Routes {
// This can be easily mounted onto a server.
val myRoutes: Resource[IO, HttpRoutes[IO]] =
SimpleRestJsonBuilder.routes(HelloWorldImpl).resource
}

A note about errors

When encountering data types annotated with the @error trait in smithy, smithy4s will ensure that the generated types extend Throwable. The interpreters are aware of it, and try to recover any error raised in your effect-types that your smithy specs know about, in order to render it correctly in Json and apply the specified status code (see the @httpError trait for this).

As a convenience, Smithy4s provides mapErrors and flatMapErrors methods, that allow to intercept exceptions that were not specified in the spec, and transform them into exceptions that were.

In particular, the smithy4s interpreters raise specific errors when they fail to decode http requests. The mapErrors and flatMapErrors methods can be used to ensure that a specified error is returned by your service:

myRoutes.mapErrors{
case e: PayloadError => MyClientError(...)
}.resource

Wiring the routes

As a reminder, to wire those routes into a server, you need something like:

import cats.effect._
import org.http4s.ember.server._
import org.http4s.implicits._
import com.comcast.ip4s._

object Main extends IOApp {

def run(args: List[String]): IO[ExitCode] =
Routes.myRoutes.flatMap { routes =>
EmberServerBuilder.default[IO]
.withPort(port"9000")
.withHost(host"localhost")
.withHttpApp(routes.orNotFound)
.build
}.use(_ => IO.never)
.as(ExitCode.Success)
}