Skip to content

Commit

Permalink
use tapir to describe and document routes
Browse files Browse the repository at this point in the history
  • Loading branch information
endertunc committed Jun 1, 2020
1 parent a7ddaba commit 6ddfcb2
Show file tree
Hide file tree
Showing 26 changed files with 385 additions and 338 deletions.
1 change: 1 addition & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ SortImports.blocks = [
"java."
"scala."
"org.http4s."
"sttp."
"cats."
"io.circe."
"doobie."
Expand Down
95 changes: 52 additions & 43 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ lazy val RandomDataGeneratorVersion = "2.8"
lazy val RefinedVersion = "0.9.14"
lazy val PgEmbededVersion = "0.13.3"
lazy val DerivingVersion = "2.0.0-M5"
lazy val TapirVersion = "0.14.3"
lazy val TapirVersion = "0.12.23"
lazy val EndpointsVersion = "0.15.0"
lazy val SortImportsVersion = "0.3.2"
lazy val ScalaTracingVersion = "2.2.0"
Expand Down Expand Up @@ -58,47 +58,56 @@ lazy val root = (project in file("."))
"com.nequissimus" %% "sort-imports" % SortImportsVersion
),
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % CatsEffectVersion,
"co.fs2" %% "fs2-core" % Fs2Version,
"com.github.pureconfig" %% "pureconfig" % PureConfigVersion,
"org.flywaydb" % "flyway-core" % FlywayVersion,
"io.chrisdavenport" %% "log4cats-slf4j" % Log4CatsVersion,
"org.http4s" %% "http4s-blaze-server" % Http4sVersion,
"org.http4s" %% "http4s-circe" % Http4sVersion,
"org.http4s" %% "http4s-dsl" % Http4sVersion,
"io.circe" %% "circe-core" % CirceVersion,
"io.circe" %% "circe-generic" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion,
"io.circe" %% "circe-literal" % CirceVersion,
"io.circe" %% "circe-shapes" % CirceVersion,
"io.circe" %% "circe-refined" % CirceVersion,
"io.circe" %% "circe-generic-extras" % CirceVersion,
"org.tpolecat" %% "doobie-core" % DoobieVersion,
"org.tpolecat" %% "doobie-postgres" % DoobieVersion,
"org.tpolecat" %% "doobie-refined" % DoobieVersion,
"org.tpolecat" %% "doobie-hikari" % DoobieVersion,
"org.tpolecat" %% "doobie-quill" % DoobieVersion,
"ch.qos.logback" % "logback-classic" % LogbackVersion,
"eu.timepit" %% "refined" % RefinedVersion,
"eu.timepit" %% "refined-pureconfig" % RefinedVersion,
"eu.timepit" %% "refined-cats" % RefinedVersion,
"org.typelevel" %% "mouse" % CatsVersion,
"com.colisweb" %% "scala-opentracing-http4s-server-tapir" % ScalaTracingVersion,
"com.beachape" %% "enumeratum" % EnumeratumVersion,
"com.beachape" %% "enumeratum-doobie" % EnumeratumDoobie,
"com.beachape" %% "enumeratum-circe" % EnumeratumCirce,
"com.beachape" %% "enumeratum-quill" % EnumeratumQiull,
"io.estatico" %% "newtype" % NewtypeVersion,
"org.scalaz" %% "deriving-macro" % DerivingVersion,
"org.scalatest" %% "scalatest" % ScalaTestVersion % Test,
"org.scalacheck" %% "scalacheck" % ScalaCheckVersion % Test,
"org.tpolecat" %% "doobie-scalatest" % DoobieVersion % Test,
"com.dimafeng" %% "testcontainers-scala-scalatest" % TestContainersScalaVersion % Test,
"com.dimafeng" %% "testcontainers-scala-postgresql" % TestContainersScalaVersion % Test,
"eu.timepit" %% "refined-scalacheck" % RefinedVersion % Test,
"com.beachape" %% "enumeratum-scalacheck" % EnumeratumScalacheck % Test,
"com.ironcorelabs" %% "cats-scalatest" % CatsScalaTestVersion % Test,
"com.opentable.components" % "otj-pg-embedded" % PgEmbededVersion % Test,
"org.typelevel" %% "cats-effect" % CatsEffectVersion,
"co.fs2" %% "fs2-core" % Fs2Version,
"com.github.pureconfig" %% "pureconfig" % PureConfigVersion,
"org.flywaydb" % "flyway-core" % FlywayVersion,
"io.chrisdavenport" %% "log4cats-slf4j" % Log4CatsVersion,
"org.http4s" %% "http4s-blaze-server" % Http4sVersion,
"org.http4s" %% "http4s-circe" % Http4sVersion,
"org.http4s" %% "http4s-dsl" % Http4sVersion,
"io.circe" %% "circe-core" % CirceVersion,
"io.circe" %% "circe-generic" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion,
"io.circe" %% "circe-literal" % CirceVersion,
"io.circe" %% "circe-shapes" % CirceVersion,
"io.circe" %% "circe-refined" % CirceVersion,
"io.circe" %% "circe-generic-extras" % CirceVersion,
"org.tpolecat" %% "doobie-core" % DoobieVersion,
"org.tpolecat" %% "doobie-postgres" % DoobieVersion,
"org.tpolecat" %% "doobie-refined" % DoobieVersion,
"org.tpolecat" %% "doobie-hikari" % DoobieVersion,
"org.tpolecat" %% "doobie-quill" % DoobieVersion,
"ch.qos.logback" % "logback-classic" % LogbackVersion,
"eu.timepit" %% "refined" % RefinedVersion,
"eu.timepit" %% "refined-pureconfig" % RefinedVersion,
"eu.timepit" %% "refined-cats" % RefinedVersion,
"org.typelevel" %% "mouse" % CatsVersion,
"com.colisweb" %% "scala-opentracing-http4s-server-tapir" % ScalaTracingVersion,
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-cats" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-refined" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-openapi-circe-yaml" % TapirVersion,
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-http4s" % TapirVersion,
"com.beachape" %% "enumeratum" % EnumeratumVersion,
"com.beachape" %% "enumeratum-doobie" % EnumeratumDoobie,
"com.beachape" %% "enumeratum-circe" % EnumeratumCirce,
"com.beachape" %% "enumeratum-quill" % EnumeratumQiull,
"io.estatico" %% "newtype" % NewtypeVersion,
"org.scalaz" %% "deriving-macro" % DerivingVersion,
"org.scalaz" %% "scalaz-deriving" % DerivingVersion,
"org.scalaz" %% "scalaz-deriving-magnolia" % DerivingVersion,
"org.scalatest" %% "scalatest" % ScalaTestVersion % Test,
"org.scalacheck" %% "scalacheck" % ScalaCheckVersion % Test,
"org.tpolecat" %% "doobie-scalatest" % DoobieVersion % Test,
"com.dimafeng" %% "testcontainers-scala-scalatest" % TestContainersScalaVersion % Test,
"com.dimafeng" %% "testcontainers-scala-postgresql" % TestContainersScalaVersion % Test,
"eu.timepit" %% "refined-scalacheck" % RefinedVersion % Test,
"com.beachape" %% "enumeratum-scalacheck" % EnumeratumScalacheck % Test,
"com.ironcorelabs" %% "cats-scalatest" % CatsScalaTestVersion % Test,
"com.opentable.components" % "otj-pg-embedded" % PgEmbededVersion % Test,
compilerPlugin("org.scalaz" %% "deriving-plugin" % DerivingVersion cross CrossVersion.full),
compilerPlugin("org.typelevel" %% "kind-projector" % KindProjectorVersion cross CrossVersion.full),
compilerPlugin("com.olegpy" %% "better-monadic-for" % BetterMonadicForVersion),
Expand All @@ -121,7 +130,7 @@ lazy val scalastyleSettings = Seq(
lazy val testSettings = Seq(
Test / parallelExecution := true,
Test / concurrentRestrictions := Seq(
Tags.limit(Tags.Test, max = 4) // scalastyle:ignore
Tags.limit(Tags.Test, max = 1) // scalastyle:ignore
),
Test / logBuffered := false,
Test / fork := true,
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<logger name="com.github.dockerjava" level="WARN"/>
<logger name="car.advert" level="TRACE"/>

<root level="WARN">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
19 changes: 14 additions & 5 deletions src/main/scala/car/advert/CarAdvertModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import org.http4s.server.Router
import org.http4s.server.middleware.{ CORSConfig, CORS, Logger => RequestResponseLogger }
import org.http4s.{ HttpApp, HttpRoutes, Response }

import sttp.tapir.docs.openapi._
import sttp.tapir.openapi.Server
import sttp.tapir.openapi.circe.yaml._
import sttp.tapir.swagger.http4s.SwaggerHttp4s

import cats.data.Kleisli._
import cats.data.{ Kleisli, OptionT }
import cats.effect.{ ConcurrentEffect, ContextShift }
Expand All @@ -17,12 +22,9 @@ import doobie.util.transactor.Transactor
import com.colisweb.tracing.core.TracingContextBuilder

import car.advert.http.CarAdvertRoutes
import car.advert.http.CarAdvertRoutes
import car.advert.repository.algebra.CarAdvertRepositoryAlgebra
import car.advert.http.ServerEndpoints
import car.advert.repository.algebra.CarAdvertRepositoryAlgebra
import car.advert.repository.postgres.doobie.DoobiePgCarAdvertRepository
import car.advert.repository.postgres.doobie.DoobiePgCarAdvertRepository
import car.advert.service.CarAdvertService
import car.advert.service.CarAdvertService
import io.chrisdavenport.log4cats.SelfAwareStructuredLogger

Expand All @@ -32,12 +34,19 @@ class CarAdvertModule[F[_]: ConcurrentEffect: ContextShift: Transactor: SelfAwar
implicit val carAdvertService: CarAdvertService[F] = CarAdvertService()
implicit val carAdvertRoutes: CarAdvertRoutes[F] = CarAdvertRoutes()

private lazy val docsRoutes: HttpRoutes[F] = toDocsRoutes(carAdvertRoutes.endpoints)

def httpApp: HttpApp[F] =
CORS(endpoints, CORSConfig(anyOrigin = true, anyMethod = true, allowCredentials = true, maxAge = 1.day.toSeconds)).orNotFound

def endpoints: HttpRoutes[F] =
// The main logger should be adjusted via env variables.
RequestResponseLogger.httpRoutes(logHeaders = true, logBody = true)(Router("/" -> carAdvertRoutes.routes)) <+>
RequestResponseLogger.httpRoutes(logHeaders = true, logBody = true)(Router("/" -> carAdvertRoutes.routes)) <+> docsRoutes <+>
RequestResponseLogger.httpRoutes(logHeaders = true, logBody = false)(Kleisli(_ => OptionT.pure(Response.notFound))) // log all NotFounds

def toDocsRoutes(es: ServerEndpoints): HttpRoutes[F] = {
val openapi = es.toList.toOpenAPI("Car Adverts", "1.0.0").servers(List(Server(s"", None)))
val yaml = openapi.toYaml
new SwaggerHttp4s(yaml).routes[F]
}
}
30 changes: 15 additions & 15 deletions src/main/scala/car/advert/http/BaseHttp4sRoute.scala
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
package car.advert.http

import org.http4s.Response
import org.http4s.circe.CirceEntityDecoder
import org.http4s.circe.CirceEntityEncoder
import org.http4s.dsl.Http4sDsl

import cats.effect.Async
import cats.implicits._

import io.circe.Encoder
import io.circe.syntax._
import com.colisweb.tracing.core.TracingContext

import car.advert.model.error.AppError
import car.advert.model.error.AppError.InternalServerError
import car.advert.model.error.Error_OUT
import io.chrisdavenport.log4cats.Logger

abstract class BaseHttp4sRoute[F[_]: Async: Logger] extends Http4sDsl[F] with CirceEntityEncoder with CirceEntityDecoder {

implicit class FToResponse[A](result: F[A]) {
def toResponse(implicit encoder: Encoder[A]): F[Response[F]] =
result
.flatMap {
case _: Unit => Ok()
case model => Ok(model.asJson)
}
.recoverWith {
case appError: AppError => appError.toHttpResponse()
case t: Exception =>
println(t)
Logger[F].error(t)(message = "Unexpected exception") *> InternalServerError("Internal Server Error")
}
private def exceptionToErrorOut(t: Throwable)(implicit tracingContext: TracingContext[F]): F[Error_OUT] =
t match {
case appError: AppError => appError.toErrorOut.pure[F]
case _ =>
Logger[F].error(t)(message = "Unexpected exception") *>
new InternalServerError("Unexpected exception").toErrorOut.pure[F]
}

def toResponse(implicit tracingContext: TracingContext[F]): F[Either[Error_OUT, A]] =
result.map(t => t.asRight[Error_OUT]).recoverWith {
case t: Throwable => exceptionToErrorOut(t).map(_.asLeft[A])
}
}

}
Loading

0 comments on commit 6ddfcb2

Please sign in to comment.