Last active
          December 17, 2022 14:02 
        
      - 
      
- 
        Save gvolpe/3fa32dd1b6abce2a5466efbf0eca9e94 to your computer and use it in GitHub Desktop. 
Revisions
- 
        gvolpe revised this gist Sep 2, 2018 . 1 changed file with 0 additions and 2 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -127,8 +127,6 @@ object HttpErrorHandler { class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F]) (implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] { private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => 
- 
        gvolpe revised this gist Sep 2, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ import cats.{ ApplicativeError, MonadError } import cats.data.{ Kleisli, OptionT } import cats.effect.Sync import cats.effect.concurrent.Ref 
- 
        gvolpe revised this gist Sep 2, 2018 . 1 changed file with 3 additions and 10 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ import cats.{ ApplicativeErrpr, MonadError } import cats.data.{ Kleisli, OptionT } import cats.effect.Sync import cats.effect.concurrent.Ref @@ -112,7 +112,7 @@ trait HttpErrorHandler[F[_], E <: Throwable] { } object RoutesHttpErrorHandler { def apply[F[_]: ApplicativeError[?[_], E], E <: Throwable](routes: HttpRoutes[F])(handler: E => F[Response[F]]): HttpRoutes[F] = Kleisli { req => OptionT { routes.run(req).value.handleErrorWith(e => handler(e).map(Option(_))) @@ -124,13 +124,6 @@ object HttpErrorHandler { def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev } class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F]) (implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] { @@ -155,7 +148,7 @@ class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F]) } } val routes: HttpRoutes[F] = H.handle(httpRoutes) } 
- 
        gvolpe revised this gist Aug 28, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -128,7 +128,7 @@ object syntax { implicit class HttpErrorHandlerOp[F[_], E <: Throwable](routes: HttpRoutes[F]) { def handleHttpErrorResponse(implicit H: HttpErrorHandler[F, E]): HttpRoutes[F] = H.handle(routes) } } class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F]) 
- 
        gvolpe revised this gist Aug 27, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -13,7 +13,7 @@ import org.http4s.dsl.Http4sDsl case class User(username: String, age: Int) case class UserUpdateAge(age: Int) sealed trait UserError extends Exception case class UserAlreadyExists(username: String) extends UserError case class UserNotFound(username: String) extends UserError case class InvalidUserAge(age: Int) extends UserError 
- 
        gvolpe revised this gist Aug 27, 2018 . 1 changed file with 47 additions and 40 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,12 @@ import cats.MonadError import cats.data.{ Kleisli, OptionT } import cats.effect.Sync import cats.effect.concurrent.Ref import cats.syntax.all._ import io.circe.generic.auto._ import io.circe.syntax._ import org.http4s._ import org.http4s.circe.CirceEntityDecoder._ import org.http4s.circe._ import org.http4s.dsl.Http4sDsl @@ -55,110 +57,115 @@ object UserInterpreter { } class UserRoutes[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { val routes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user.asJson) case None => NotFound(username.asJson) } case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username.asJson) } case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Ok(username.asJson) } } } class UserRoutesAlt[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { val routes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user.asJson) case None => NotFound(username.asJson) } case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username.asJson) }.handleErrorWith { // compiles without giving you "match non-exhaustive" error case UserAlreadyExists(username) => Conflict(username.asJson) } case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Ok(username.asJson) }.handleErrorWith { // compiles without giving you "match non-exhaustive" error case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson) } } } trait HttpErrorHandler[F[_], E <: Throwable] { def handle(routes: HttpRoutes[F]): HttpRoutes[F] } object RoutesHttpErrorHandler { def apply[F[_]: MonadError[?[_], E], E <: Throwable](routes: HttpRoutes[F])(handler: E => F[Response[F]]): HttpRoutes[F] = Kleisli { req => OptionT { routes.run(req).value.handleErrorWith(e => handler(e).map(Option(_))) } } } object HttpErrorHandler { def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev } object syntax { implicit class HttpErrorHandlerOp[F[_], E <: Throwable](routes: HttpRoutes[F]) { def handleHttpErrorResponse(implicit H: HttpErrorHandler[F, E]): HttpRoutes[F] = H.handle(routes) } } class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F]) (implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] { import syntax._ private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user.asJson) case None => NotFound(username.asJson) } case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username.asJson) } case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Created(username.asJson) } } val routes: HttpRoutes[F] = httpRoutes.handleHttpErrorResponse } class UserHttpErrorHandler[F[_]: MonadError[?[_], UserError]] extends HttpErrorHandler[F, UserError] with Http4sDsl[F] { private val handler: UserError => F[Response[F]] = { case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson) case UserAlreadyExists(username) => Conflict(username.asJson) case UserNotFound(username) => NotFound(username.asJson) } override def handle(routes: HttpRoutes[F]): HttpRoutes[F] = RoutesHttpErrorHandler(routes)(handler) } 
- 
        gvolpe revised this gist Aug 25, 2018 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,7 +8,6 @@ import org.http4s._ import org.http4s.circe._ import org.http4s.dsl.Http4sDsl case class User(username: String, age: Int) case class UserUpdateAge(age: Int) 
- 
        gvolpe created this gist Aug 25, 2018 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,165 @@ import cats.MonadError import cats.effect.Sync import cats.effect.concurrent.Ref import cats.syntax.all._ import io.circe._ import io.circe.generic.auto._ import org.http4s._ import org.http4s.circe._ import org.http4s.dsl.Http4sDsl // format: off case class User(username: String, age: Int) case class UserUpdateAge(age: Int) sealed trait UserError extends Throwable case class UserAlreadyExists(username: String) extends UserError case class UserNotFound(username: String) extends UserError case class InvalidUserAge(age: Int) extends UserError trait UserAlgebra[F[_]] { def find(username: String): F[Option[User]] def save(user: User): F[Unit] def updateAge(username: String, age: Int): F[Unit] } object UserInterpreter { def create[F[_]](implicit F: Sync[F]): F[UserAlgebra[F]] = Ref.of[F, Map[String, User]](Map.empty).map { state => new UserAlgebra[F] { private def validateAge(age: Int): F[Unit] = if (age <= 0) F.raiseError(InvalidUserAge(age)) else F.unit override def find(username: String): F[Option[User]] = state.get.map(_.get(username)) override def save(user: User): F[Unit] = validateAge(user.age) *> find(user.username).flatMap { case Some(_) => F.raiseError(UserAlreadyExists(user.username)) case None => state.update(_.updated(user.username, user)) } override def updateAge(username: String, age: Int): F[Unit] = validateAge(age) *> find(username).flatMap { case Some(user) => state.update(_.updated(username, user.copy(age = age))) case None => F.raiseError(UserNotFound(username)) } } } } object JsonCodecs { implicit def jsonDecoder[A <: Product: Decoder, F[_]: Sync]: EntityDecoder[F, A] = jsonOf[F, A] implicit def jsonEncoder[A <: Product: Encoder, F[_]: Sync]: EntityEncoder[F, A] = jsonEncoderOf[F, A] } class UserRoutes[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { import JsonCodecs._ val routes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user) case None => NotFound(username) } case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username) } case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Ok(username) } } } class UserRoutesAlt[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { import JsonCodecs._ val routes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user) case None => NotFound(username) } case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username) }.handleErrorWith { // compiles without giving you "match non-exhaustive" error case UserAlreadyExists(username) => Conflict(username) } case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Ok(username) }.handleErrorWith { // compiles without giving you "match non-exhaustive" error case InvalidUserAge(age) => BadRequest(s"Invalid age $age") } } } trait HttpErrorHandler[F[_], E <: Throwable] { def handle(fa: F[Response[F]]): F[Response[F]] } object HttpErrorHandler { def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev } object syntax { implicit class HttpErrorHandlerOps[F[_], E <: Throwable](fa: F[Response[F]]) { def handleHttpErrorResponse(implicit H: HttpErrorHandler[F, E]): F[Response[F]] = H.handle(fa) } } class UserRoutesMTL[F[_]: Sync: HttpErrorHandler[?[_], UserError]](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] { import JsonCodecs._ import syntax._ val routes: HttpRoutes[F] = HttpRoutes.of[F] { case GET -> Root / "users" / username => userAlgebra.find(username).flatMap { case Some(user) => Ok(user) case None => NotFound(username) }.handleHttpErrorResponse case req @ POST -> Root / "users" => req.as[User].flatMap { user => userAlgebra.save(user) *> Created(user.username) }.handleHttpErrorResponse case req @ PUT -> Root / "users" / username => req.as[UserUpdateAge].flatMap { userUpdate => userAlgebra.updateAge(username, userUpdate.age) *> Created(username) }.handleHttpErrorResponse } } class UserHttpErrorHandler[F[_]: MonadError[?[_], UserError]] extends HttpErrorHandler[F, UserError] with Http4sDsl[F] { // match may not be exhaustive override def handle(fa: F[Response[F]]): F[Response[F]] = fa.handleErrorWith { // It would fail on the following inputs: UserAlreadyExists(_), UserNotFound(_) case InvalidUserAge(age) => BadRequest(s"Invalid age $age") } }