Add error handling

master
Andriy Kushnir (Orhideous) 2019-12-22 14:16:20 +02:00
parent edf110e37a
commit 09146d610a
No known key found for this signature in database
GPG Key ID: 62E078AB621B0D15
6 changed files with 66 additions and 8 deletions

View File

@ -6,6 +6,7 @@ val LogbackVersion = "1.2.3"
val BetterFilesVersion = "3.8.0"
val DirectoryWatcherVersion = "0.9.6"
val ScalaLoggingVersion = "3.9.2"
val MeowMTLVersion = "0.4.0"
organization := "name.orhideous"
name := "twicher"
@ -43,6 +44,7 @@ libraryDependencies ++= Seq(
"io.methvin" %% "directory-watcher-better-files" % DirectoryWatcherVersion,
"com.typesafe.scala-logging" %% "scala-logging" % ScalaLoggingVersion,
"ch.qos.logback" % "logback-classic" % LogbackVersion,
"com.olegpy" %% "meow-mtl-core" % MeowMTLVersion,
compilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3"),
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.0")
)

View File

@ -4,18 +4,24 @@ import cats.effect.ExitCode
import cats.effect.IO
import cats.effect.IOApp
import cats.implicits._
import name.orhideous.twicher.error.HttpErrorHandler
import name.orhideous.twicher.error.QuoteHttpErrorHandler
import name.orhideous.twicher.error.TwicherError
import name.orhideous.twicher.persistence.QuotesFileRepository
import org.http4s.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze._
import com.olegpy.meow.hierarchy._
object Main extends IOApp {
private val host = sys.env.getOrElse("TWICHER_HOST", "0.0.0.0")
private val port = sys.env.getOrElse("TWICHER_PORT", "9000").toInt
private val dir = sys.env.getOrElse("TWICHER_DIR", "./data")
private implicit val errorHandler: HttpErrorHandler[IO, TwicherError] = new QuoteHttpErrorHandler[IO]
private val repository = QuotesFileRepository[IO](dir)
private val routes = Router[IO]("/" -> Routes[IO](repository)).orNotFound
private val routes = Router[IO]("/" -> new Routes[IO, TwicherError](repository).routes).orNotFound
override def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO]

View File

@ -3,14 +3,17 @@ package name.orhideous.twicher
import cats.effect.Sync
import cats.implicits._
import io.circe.generic.auto._
import name.orhideous.twicher.persistence.QuotesAlgebra
import org.http4s._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
class Routes[F[_]: Sync](algebra: QuotesAlgebra[F]) extends Http4sDsl[F] {
import name.orhideous.twicher.error.HttpErrorHandler
import name.orhideous.twicher.persistence.QuotesAlgebra
val routes: HttpRoutes[F] = HttpRoutes.of[F] {
class Routes[F[_]: Sync, E <: Throwable](algebra: QuotesAlgebra[F])(implicit H: HttpErrorHandler[F, E])
extends Http4sDsl[F] {
private val rawRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
case GET -> Root =>
algebra.list.flatMap(Ok(_))
@ -20,8 +23,5 @@ class Routes[F[_]: Sync](algebra: QuotesAlgebra[F]) extends Http4sDsl[F] {
case GET -> Root / IntVar(id) =>
algebra.read(id).flatMap(Ok(_))
}
}
object Routes {
def apply[F[_]: Sync](algebra: QuotesAlgebra[F]): HttpRoutes[F] = new Routes(algebra).routes
val routes: HttpRoutes[F] = H.handle(rawRoutes)
}

View File

@ -0,0 +1,7 @@
package name.orhideous.twicher.error
import org.http4s.HttpRoutes
trait HttpErrorHandler[F[_], E <: Throwable] {
def handle(routes: HttpRoutes[F]): HttpRoutes[F]
}

View File

@ -0,0 +1,20 @@
package name.orhideous.twicher.error
import cats.ApplicativeError
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
class QuoteHttpErrorHandler[F[_]](implicit M: ApplicativeError[F, TwicherError])
extends HttpErrorHandler[F, TwicherError]
with RoutesHttpErrorWrapper[F, TwicherError]
with Http4sDsl[F] {
private val handler: Handler = {
case TwicherError.NoQuotes => InternalServerError()
case TwicherError.NoSuchQuote => NotFound()
}
override def handle(routes: HttpRoutes[F]): HttpRoutes[F] =
wrapWith(handler)(routes)
}

View File

@ -0,0 +1,23 @@
package name.orhideous.twicher.error
import cats.data.Kleisli
import cats.data.OptionT
import cats.ApplicativeError
import org.http4s.HttpRoutes
import org.http4s.Request
import org.http4s.Response
import cats.implicits._
trait RoutesHttpErrorWrapper[F[_], E <: Throwable] {
protected type Handler = E => F[Response[F]]
protected def wrapWith(handler: Handler)(routes: HttpRoutes[F])(implicit ev: ApplicativeError[F, E]): HttpRoutes[F] =
Kleisli { req: Request[F] =>
OptionT {
routes
.run(req)
.value
.handleErrorWith(handler(_).map(Option(_)))
}
}
}