minor improvements
This commit is contained in:
parent
c25f2e0810
commit
01f2676ca3
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -65,7 +65,7 @@ jobs:
|
|||||||
- name: Migrate
|
- name: Migrate
|
||||||
run: csbt flyway/flywayMigrate
|
run: csbt flyway/flywayMigrate
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: csbt "scalafmtCheckAll;scalafixAll --check"
|
run: csbt lint-check
|
||||||
- name: Compile
|
- name: Compile
|
||||||
run: |
|
run: |
|
||||||
csbt "compile; test:compile"
|
csbt "compile; test:compile"
|
||||||
|
@ -217,6 +217,9 @@ inThisBuild(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
addCommandAlias("lint-check", "scalafmtCheckAll; scalafixAll --check")
|
||||||
|
addCommandAlias("lint-run", "scalafmtAll; scalafixAll")
|
||||||
|
|
||||||
wartremoverErrors in (Compile, compile) ++=
|
wartremoverErrors in (Compile, compile) ++=
|
||||||
Warts.allBut(
|
Warts.allBut(
|
||||||
Wart.Any,
|
Wart.Any,
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package wow.doge.http4sdemo
|
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.effect.Sync
|
|
||||||
import cats.implicits._
|
|
||||||
import io.circe.Decoder
|
|
||||||
import io.circe.Encoder
|
|
||||||
import io.circe.generic.semiauto._
|
|
||||||
import monix.bio.Task
|
|
||||||
import org.http4s.Method._
|
|
||||||
import org.http4s._
|
|
||||||
import org.http4s.circe._
|
|
||||||
import org.http4s.client.Client
|
|
||||||
import org.http4s.client.dsl.Http4sClientDsl
|
|
||||||
import org.http4s.implicits._
|
|
||||||
|
|
||||||
sealed trait Jokes[F[_]] {
|
|
||||||
def get: F[Jokes.Joke]
|
|
||||||
}
|
|
||||||
|
|
||||||
object Jokes {
|
|
||||||
def apply[F[_]](implicit ev: Jokes[F]): Jokes[F] = ev
|
|
||||||
|
|
||||||
final case class Joke(joke: String)
|
|
||||||
object Joke {
|
|
||||||
implicit val jokeDecoder: Decoder[Joke] = deriveDecoder[Joke]
|
|
||||||
implicit def jokeEntityDecoder[F[_]: Sync]: EntityDecoder[F, Joke] =
|
|
||||||
jsonOf
|
|
||||||
implicit val jokeEncoder: Encoder[Joke] = deriveEncoder[Joke]
|
|
||||||
implicit def jokeEntityEncoder[F[_]: Applicative]: EntityEncoder[F, Joke] =
|
|
||||||
jsonEncoderOf
|
|
||||||
}
|
|
||||||
|
|
||||||
final case class JokeError(e: Throwable) extends RuntimeException
|
|
||||||
|
|
||||||
def impl(C: Client[Task]): Jokes[Task] = new Jokes[Task] {
|
|
||||||
val dsl = new Http4sClientDsl[Task] {}
|
|
||||||
import dsl._
|
|
||||||
def get: Task[Jokes.Joke] = {
|
|
||||||
C.expect[Joke](GET(uri"https://icanhazdadjoke.com/"))
|
|
||||||
.adaptError { case t =>
|
|
||||||
JokeError(t)
|
|
||||||
} // Prevent Client Json Decoding Failure Leaking
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +1,38 @@
|
|||||||
package wow.doge.http4sdemo
|
package wow.doge.http4sdemo
|
||||||
|
|
||||||
|
import scala.concurrent.duration.MILLISECONDS
|
||||||
|
|
||||||
import cats.effect.ExitCode
|
import cats.effect.ExitCode
|
||||||
import cats.effect.Resource
|
import cats.effect.Resource
|
||||||
|
import io.odin.Level
|
||||||
|
import io.odin.consoleLogger
|
||||||
|
import io.odin.formatter.Formatter
|
||||||
|
import io.odin.syntax._
|
||||||
import monix.bio.BIOApp
|
import monix.bio.BIOApp
|
||||||
|
import monix.bio.IO
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import monix.bio.UIO
|
import monix.bio.UIO
|
||||||
import slick.jdbc.JdbcProfile
|
import slick.jdbc.JdbcProfile
|
||||||
|
|
||||||
object Main extends BIOApp {
|
object Main extends BIOApp {
|
||||||
val profile: JdbcProfile = slick.jdbc.PostgresProfile
|
val profile: JdbcProfile = slick.jdbc.PostgresProfile
|
||||||
def app = for {
|
val app = for {
|
||||||
|
startTime <- Resource.liftF(IO.clock.realTime(MILLISECONDS))
|
||||||
|
_ <- Resource.liftF(Task(println("""
|
||||||
|
| .__ __ __ _____ .___
|
||||||
|
| | |___/ |__/ |_______ / | | ______ __| _/____ _____ ____
|
||||||
|
| | | \ __\ __\____ \ / | |_/ ___/ ______ / __ |/ __ \ / \ / _ \
|
||||||
|
| | Y \ | | | | |_> > ^ /\___ \ /_____/ / /_/ \ ___/| Y Y ( <_> )
|
||||||
|
| |___| /__| |__| | __/\____ |/____ > \____ |\___ >__|_| /\____/
|
||||||
|
| \/ |__| |__| \/ \/ \/ \/
|
||||||
|
""".stripMargin)))
|
||||||
|
logger <- consoleLogger[Task](
|
||||||
|
formatter = Formatter.colorful,
|
||||||
|
minLevel = Level.Debug
|
||||||
|
).withAsync()
|
||||||
|
_ <- Resource.liftF(
|
||||||
|
logger.info(s"Starting ${BuildInfo.name}-${BuildInfo.version}")
|
||||||
|
)
|
||||||
db <- SlickResource("myapp.database")
|
db <- SlickResource("myapp.database")
|
||||||
_ <- Resource.liftF(for {
|
_ <- Resource.liftF(for {
|
||||||
config <- JdbcDatabaseConfig.loadFromGlobal("myapp.database")
|
config <- JdbcDatabaseConfig.loadFromGlobal("myapp.database")
|
||||||
@ -17,9 +40,10 @@ object Main extends BIOApp {
|
|||||||
} yield ())
|
} yield ())
|
||||||
_ <- Resource.liftF(
|
_ <- Resource.liftF(
|
||||||
Task.deferAction(implicit s =>
|
Task.deferAction(implicit s =>
|
||||||
Http4sdemoServer.stream(db, profile).compile.drain
|
new Server(db, profile, logger).stream.compile.drain
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
} yield ()
|
} yield ()
|
||||||
def run(args: List[String]) = {
|
def run(args: List[String]) = {
|
||||||
app
|
app
|
||||||
|
@ -2,6 +2,7 @@ package wow.doge.http4sdemo
|
|||||||
|
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import fs2.Stream
|
import fs2.Stream
|
||||||
|
import io.odin
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import monix.execution.Scheduler
|
import monix.execution.Scheduler
|
||||||
import org.http4s.client.blaze.BlazeClientBuilder
|
import org.http4s.client.blaze.BlazeClientBuilder
|
||||||
@ -10,36 +11,29 @@ import org.http4s.server.blaze.BlazeServerBuilder
|
|||||||
import org.http4s.server.middleware.Logger
|
import org.http4s.server.middleware.Logger
|
||||||
import slick.jdbc.JdbcBackend.DatabaseDef
|
import slick.jdbc.JdbcBackend.DatabaseDef
|
||||||
import slick.jdbc.JdbcProfile
|
import slick.jdbc.JdbcProfile
|
||||||
|
import wow.doge.http4sdemo.routes.LibraryRoutes
|
||||||
import wow.doge.http4sdemo.services.LibraryDbio
|
import wow.doge.http4sdemo.services.LibraryDbio
|
||||||
import wow.doge.http4sdemo.services.LibraryServiceImpl
|
import wow.doge.http4sdemo.services.LibraryServiceImpl
|
||||||
|
|
||||||
object Http4sdemoServer {
|
final class Server(db: DatabaseDef, p: JdbcProfile, logger: odin.Logger[Task]) {
|
||||||
|
|
||||||
def stream(
|
def stream(implicit s: Scheduler): Stream[Task, Nothing] = {
|
||||||
db: DatabaseDef,
|
val logger = io.odin.consoleLogger[Task](formatter =
|
||||||
p: JdbcProfile
|
io.odin.formatter.Formatter.colorful
|
||||||
)(implicit s: Scheduler): Stream[Task, Nothing] = {
|
)
|
||||||
|
val log: String => Task[Unit] = str => logger.debug(str)
|
||||||
for {
|
for {
|
||||||
client <- BlazeClientBuilder[Task](s).stream
|
client <- BlazeClientBuilder[Task](s).stream
|
||||||
helloWorldAlg = HelloWorld.impl
|
|
||||||
jokeAlg = Jokes.impl(client)
|
|
||||||
// Combine Service Routes into an HttpApp.
|
|
||||||
// Can also be done via a Router if you
|
|
||||||
// want to extract a segments not checked
|
|
||||||
// in the underlying routes.
|
|
||||||
|
|
||||||
libraryDbio = new LibraryDbio(p)
|
libraryDbio = new LibraryDbio(p)
|
||||||
libraryService = new LibraryServiceImpl(p, libraryDbio, db)
|
libraryService = new LibraryServiceImpl(p, libraryDbio, db)
|
||||||
httpApp = (
|
httpApp = (
|
||||||
Http4sdemoRoutes.helloWorldRoutes[Task](helloWorldAlg) <+>
|
new LibraryRoutes(libraryService, logger).routes
|
||||||
Http4sdemoRoutes.jokeRoutes[Task](jokeAlg) <+>
|
|
||||||
Http4sdemoRoutes.libraryRoutes(libraryService)
|
|
||||||
).orNotFound
|
).orNotFound
|
||||||
|
finalHttpApp = Logger.httpApp(
|
||||||
// With Middlewares in place
|
true,
|
||||||
finalHttpApp = Logger.httpApp(true, true)(httpApp)
|
true,
|
||||||
// _ = {finalHttpApp.run(Request.)}
|
logAction = log.pure[Option]
|
||||||
|
)(httpApp)
|
||||||
exitCode <- BlazeServerBuilder[Task](s)
|
exitCode <- BlazeServerBuilder[Task](s)
|
||||||
.bindHttp(8081, "0.0.0.0")
|
.bindHttp(8081, "0.0.0.0")
|
||||||
.withHttpApp(finalHttpApp)
|
.withHttpApp(finalHttpApp)
|
@ -6,7 +6,5 @@ import slick.jdbc.JdbcBackend.Database
|
|||||||
|
|
||||||
object SlickResource {
|
object SlickResource {
|
||||||
def apply(confPath: String) =
|
def apply(confPath: String) =
|
||||||
Resource.make(Task(Database.forConfig(confPath)))(db =>
|
Resource.make(Task(Database.forConfig(confPath)))(db => Task(db.close()))
|
||||||
Task(db.source.close()) >> Task(db.close())
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -101,4 +101,5 @@ object BookSearchMode extends Enum[BookSearchMode] {
|
|||||||
withNameEither(s).leftMap(e => ParseFailure(e.getMessage, e.getMessage))
|
withNameEither(s).leftMap(e => ParseFailure(e.getMessage, e.getMessage))
|
||||||
)
|
)
|
||||||
object Matcher extends QueryParamDecoderMatcher[BookSearchMode]("mode")
|
object Matcher extends QueryParamDecoderMatcher[BookSearchMode]("mode")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package wow.doge.http4sdemo
|
|||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
|
import io.odin.meta.Position
|
||||||
|
import io.odin.meta.Render
|
||||||
import monix.bio.IO
|
import monix.bio.IO
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import monix.reactive.Observable
|
import monix.reactive.Observable
|
||||||
@ -35,4 +37,18 @@ package object implicits {
|
|||||||
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
|
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit final class OdinLoggerExt(private val logger: io.odin.Logger[Task])
|
||||||
|
extends AnyVal {
|
||||||
|
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||||
|
logger.debug(msg).hideErrors
|
||||||
|
def infoU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||||
|
logger.info(msg).hideErrors
|
||||||
|
def traceU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||||
|
logger.trace(msg).hideErrors
|
||||||
|
def warnU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||||
|
logger.warn(msg).hideErrors
|
||||||
|
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||||
|
logger.error(msg).hideErrors
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,29 @@
|
|||||||
package wow.doge.http4sdemo
|
package wow.doge.http4sdemo.routes
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import cats.implicits._
|
|
||||||
import fs2.interop.reactivestreams._
|
import fs2.interop.reactivestreams._
|
||||||
import io.circe.Codec
|
import io.circe.Codec
|
||||||
import io.circe.generic.semiauto._
|
import io.circe.generic.semiauto._
|
||||||
|
import io.odin.Logger
|
||||||
import monix.bio.IO
|
import monix.bio.IO
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import monix.bio.UIO
|
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
import wow.doge.http4sdemo.dto.Book
|
import wow.doge.http4sdemo.dto.Book
|
||||||
import wow.doge.http4sdemo.dto.BookSearchMode
|
import wow.doge.http4sdemo.dto.BookSearchMode
|
||||||
import wow.doge.http4sdemo.dto.BookUpdate
|
import wow.doge.http4sdemo.dto.BookUpdate
|
||||||
import wow.doge.http4sdemo.dto.NewBook
|
import wow.doge.http4sdemo.dto.NewBook
|
||||||
|
import wow.doge.http4sdemo.implicits._
|
||||||
import wow.doge.http4sdemo.services.LibraryService
|
import wow.doge.http4sdemo.services.LibraryService
|
||||||
|
|
||||||
object Http4sdemoRoutes {
|
class LibraryRoutes(libraryService: LibraryService, logger: Logger[Task]) {
|
||||||
|
|
||||||
def jokeRoutes[F[_]: Sync](J: Jokes[F]): HttpRoutes[F] = {
|
val routes: HttpRoutes[Task] = {
|
||||||
val dsl = Http4sDsl[F]
|
|
||||||
import dsl._
|
|
||||||
HttpRoutes.of[F] { case GET -> Root / "joke" =>
|
|
||||||
for {
|
|
||||||
joke <- J.get
|
|
||||||
resp <- Ok(joke)
|
|
||||||
} yield resp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def helloWorldRoutes[F[_]: Sync](H: HelloWorld[F]): HttpRoutes[F] = {
|
|
||||||
val dsl = new Http4sDsl[F] {}
|
|
||||||
import dsl._
|
|
||||||
HttpRoutes.of[F] { case GET -> Root / "hello" / name =>
|
|
||||||
for {
|
|
||||||
greeting <- H.hello(HelloWorld.Name(name))
|
|
||||||
resp <- Ok(greeting)
|
|
||||||
r2 <- BadRequest("Bad request")
|
|
||||||
} yield r2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def libraryRoutes(libraryService: LibraryService): HttpRoutes[Task] = {
|
|
||||||
val dsl = Http4sDsl[Task]
|
val dsl = Http4sDsl[Task]
|
||||||
import dsl._
|
import dsl._
|
||||||
object Value extends QueryParamDecoderMatcher[String]("value")
|
object Value extends QueryParamDecoderMatcher[String]("value")
|
||||||
HttpRoutes.of[Task] {
|
HttpRoutes.of[Task] {
|
||||||
|
|
||||||
case GET -> Root / "api" / "get" / "book" :?
|
case GET -> Root / "api" / "books" :?
|
||||||
BookSearchMode.Matcher(mode) +& Value(value) =>
|
BookSearchMode.Matcher(mode) +& Value(value) =>
|
||||||
import org.http4s.circe.streamJsonArrayEncoder
|
import org.http4s.circe.streamJsonArrayEncoder
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
@ -63,7 +39,7 @@ object Http4sdemoRoutes {
|
|||||||
} yield res
|
} yield res
|
||||||
)
|
)
|
||||||
|
|
||||||
case GET -> Root / "api" / "get" / "books" =>
|
case GET -> Root / "api" / "books" =>
|
||||||
import org.http4s.circe.streamJsonArrayEncoder
|
import org.http4s.circe.streamJsonArrayEncoder
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
Task.deferAction(implicit s =>
|
Task.deferAction(implicit s =>
|
||||||
@ -73,67 +49,50 @@ object Http4sdemoRoutes {
|
|||||||
.toStream[Task]
|
.toStream[Task]
|
||||||
)
|
)
|
||||||
res <- Ok(books.map(_.asJson))
|
res <- Ok(books.map(_.asJson))
|
||||||
// res <- Ok(streamJsonArrayEncoderOf[Task, Book].(books))
|
|
||||||
} yield res
|
} yield res
|
||||||
)
|
)
|
||||||
|
|
||||||
case GET -> Root / "blah" => Ok().hideErrors
|
case GET -> Root / "api" / "books" / IntVar(id) =>
|
||||||
|
|
||||||
case GET -> Root / "api" / "get" / "book" / IntVar(id) =>
|
|
||||||
import org.http4s.circe.CirceEntityCodec._
|
import org.http4s.circe.CirceEntityCodec._
|
||||||
// import org.http4s.circe.jsonEncoder
|
|
||||||
// import io.circe.syntax._
|
|
||||||
for {
|
for {
|
||||||
bookJson <- libraryService.getBookById(id)
|
bookJson <- libraryService.getBookById(id)
|
||||||
res <- Ok(bookJson)
|
res <- Ok(bookJson)
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
case req @ POST -> Root / "api" / "post" / "book" =>
|
case req @ PUT -> Root / "api" / "books" =>
|
||||||
import org.http4s.circe.CirceEntityCodec._
|
import org.http4s.circe.CirceEntityCodec._
|
||||||
for {
|
for {
|
||||||
newBook <- req.as[NewBook]
|
newBook <- req.as[NewBook]
|
||||||
// .onErrorHandleWith {
|
|
||||||
// case ParseF
|
|
||||||
// }
|
|
||||||
res <- libraryService
|
res <- libraryService
|
||||||
.insertBook(newBook)
|
.insertBook(newBook)
|
||||||
|
.tapError(err => logger.errorU(err.toString))
|
||||||
.flatMap(book => Created(book).hideErrors)
|
.flatMap(book => Created(book).hideErrors)
|
||||||
.mapErrorPartialWith {
|
.onErrorHandleWith(_.toResponse)
|
||||||
case LibraryService.EntityDoesNotExist(message) =>
|
|
||||||
BadRequest(message).hideErrors
|
|
||||||
case LibraryService.EntityAlreadyExists(message) =>
|
|
||||||
BadRequest(message).hideErrors
|
|
||||||
// case LibraryService.MyError2(_) => Ok().hideErrors
|
|
||||||
// case C3 => Ok().hideErrors
|
|
||||||
}
|
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
case req @ PATCH -> Root / "api" / "update" / "book" / IntVar(id) =>
|
case req @ PATCH -> Root / "api" / "books" / IntVar(id) =>
|
||||||
import org.http4s.circe.CirceEntityCodec._
|
import org.http4s.circe.CirceEntityCodec._
|
||||||
for {
|
for {
|
||||||
updateData <- req.as[BookUpdate]
|
updateData <- req.as[BookUpdate]
|
||||||
res <- libraryService
|
res <- libraryService
|
||||||
.updateBook(id, updateData)
|
.updateBook(id, updateData)
|
||||||
.flatMap(_ => Ok().hideErrors)
|
.flatMap(_ => NoContent().hideErrors)
|
||||||
.tapError(err => UIO(println(s"Handled -> ${err.toString}")))
|
.tapError(err => logger.errorU(err.toString))
|
||||||
.mapErrorPartialWith {
|
.onErrorHandleWith(_.toResponse)
|
||||||
case e @ LibraryService.EntityDoesNotExist(message) =>
|
|
||||||
BadRequest(e: LibraryService.Error).hideErrors
|
|
||||||
// case LibraryService.MyError2(_) => Ok().hideErrors
|
|
||||||
// case C3 => Ok().hideErrors
|
|
||||||
}
|
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
case req @ DELETE -> Root / "api" / "delete" / "book" / IntVar(id) =>
|
case req @ DELETE -> Root / "api" / "books" / IntVar(id) =>
|
||||||
for {
|
for {
|
||||||
_ <- libraryService.deleteBook(id)
|
_ <- libraryService.deleteBook(id)
|
||||||
res <- Ok()
|
res <- Ok()
|
||||||
} yield res
|
} yield res
|
||||||
|
|
||||||
case req @ POST -> Root / "api" / "post" / "books" / "read" =>
|
//TODO: use convenience method for decoding json stream
|
||||||
|
case req @ POST -> Root / "api" / "books" =>
|
||||||
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
|
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
|
||||||
for {
|
for {
|
||||||
newBook <- req.as[List[Book]]
|
newBooks <- req.as[List[Book]]
|
||||||
|
// obs = Observable.fromIterable(newBooks)
|
||||||
// book <- libraryService.insertBook(newBook)
|
// book <- libraryService.insertBook(newBook)
|
||||||
res <- Ok("blah")
|
res <- Ok("blah")
|
||||||
} yield res
|
} yield res
|
@ -5,6 +5,7 @@ import monix.bio.IO
|
|||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import monix.bio.UIO
|
import monix.bio.UIO
|
||||||
import monix.reactive.Observable
|
import monix.reactive.Observable
|
||||||
|
import org.http4s.dsl.Http4sDsl
|
||||||
import slick.jdbc.JdbcBackend
|
import slick.jdbc.JdbcBackend
|
||||||
import slick.jdbc.JdbcProfile
|
import slick.jdbc.JdbcProfile
|
||||||
import wow.doge.http4sdemo.dto.Author
|
import wow.doge.http4sdemo.dto.Author
|
||||||
@ -22,14 +23,31 @@ object LibraryService {
|
|||||||
sealed trait Error extends Exception {
|
sealed trait Error extends Exception {
|
||||||
def message: String
|
def message: String
|
||||||
override def getMessage(): String = message
|
override def getMessage(): String = message
|
||||||
|
def toResponse = {
|
||||||
|
val dsl = Http4sDsl[Task]
|
||||||
|
import org.http4s.circe.CirceEntityCodec._
|
||||||
|
import dsl._
|
||||||
|
implicit val codec = Error.codec
|
||||||
|
this match {
|
||||||
|
case e @ LibraryService.EntityDoesNotExist(message) =>
|
||||||
|
NotFound(e: LibraryService.Error).hideErrors
|
||||||
|
case e @ LibraryService.EntityAlreadyExists(message) =>
|
||||||
|
BadRequest(e: LibraryService.Error).hideErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final case class EntityDoesNotExist(message: String) extends Error
|
final case class EntityDoesNotExist(message: String) extends Error
|
||||||
final case class EntityAlreadyExists(message: String) extends Error
|
final case class EntityAlreadyExists(message: String) extends Error
|
||||||
|
// final case class MessageBodyError(cause: MessageBodyFailure) extends Error
|
||||||
// final case class MyError2(message: String) extends Error
|
// final case class MyError2(message: String) extends Error
|
||||||
// case object C3 extends Error { val message: String = "C3" }
|
// case object C3 extends Error { val message: String = "C3" }
|
||||||
|
|
||||||
object Error {
|
object Error {
|
||||||
implicit val codec = deriveCodec[Error]
|
implicit val codec = deriveCodec[Error]
|
||||||
|
// def convert(e: MessageBodyFailure) = e match {
|
||||||
|
// case InvalidMessageBodyFailure(details, cause) => ()
|
||||||
|
// case MalformedMessageBodyFailure(details, cause) => ()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,14 @@ import monix.bio.UIO
|
|||||||
import monix.reactive.Observable
|
import monix.reactive.Observable
|
||||||
import org.http4s.Method
|
import org.http4s.Method
|
||||||
import org.http4s.Request
|
import org.http4s.Request
|
||||||
|
import org.http4s.Status
|
||||||
import org.http4s.Uri
|
import org.http4s.Uri
|
||||||
import org.http4s.implicits._
|
import org.http4s.implicits._
|
||||||
import wow.doge.http4sdemo.MonixBioSuite
|
import wow.doge.http4sdemo.MonixBioSuite
|
||||||
import wow.doge.http4sdemo.dto.Book
|
import wow.doge.http4sdemo.dto.Book
|
||||||
import wow.doge.http4sdemo.dto.BookSearchMode
|
import wow.doge.http4sdemo.dto.BookSearchMode
|
||||||
import wow.doge.http4sdemo.dto.BookUpdate
|
import wow.doge.http4sdemo.dto.BookUpdate
|
||||||
|
import wow.doge.http4sdemo.routes.LibraryRoutes
|
||||||
import wow.doge.http4sdemo.services.LibraryService
|
import wow.doge.http4sdemo.services.LibraryService
|
||||||
import wow.doge.http4sdemo.services.NoopLibraryService
|
import wow.doge.http4sdemo.services.NoopLibraryService
|
||||||
|
|
||||||
@ -34,12 +36,12 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
_ <- UIO.unit
|
_ <- UIO.unit
|
||||||
routes = Http4sdemoRoutes.libraryRoutes(service)
|
routes = new LibraryRoutes(service, noopLogger).routes
|
||||||
res <- routes
|
res <- routes
|
||||||
.run(Request[Task](Method.GET, uri"/api/get/books"))
|
.run(Request[Task](Method.GET, uri"/api/books"))
|
||||||
.value
|
.value
|
||||||
.hideErrors
|
.hideErrors
|
||||||
body <- res.map(_.as[List[Book]]).sequence
|
body <- res.traverse(_.as[List[Book]])
|
||||||
_ <- UIO(assertEquals(body, Some(List(book))))
|
_ <- UIO(assertEquals(body, Some(List(book))))
|
||||||
// _ <- logger2.debug(body.toString).hideErrors
|
// _ <- logger2.debug(body.toString).hideErrors
|
||||||
} yield ()
|
} yield ()
|
||||||
@ -57,15 +59,16 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
for {
|
for {
|
||||||
_ <- UIO.unit
|
_ <- UIO.unit
|
||||||
reqBody = BookUpdate(Some("blah"), None)
|
reqBody = BookUpdate(Some("blah"), None)
|
||||||
routes = Http4sdemoRoutes.libraryRoutes(service)
|
routes = new LibraryRoutes(service, noopLogger).routes
|
||||||
res <- routes
|
res <- routes
|
||||||
.run(
|
.run(
|
||||||
Request[Task](Method.PATCH, Root / "api" / "update" / "book" / "1")
|
Request[Task](Method.PATCH, Root / "api" / "books" / "1")
|
||||||
.withEntity(reqBody)
|
.withEntity(reqBody)
|
||||||
)
|
)
|
||||||
.value
|
.value
|
||||||
.hideErrors
|
.hideErrors
|
||||||
body <- res.map(_.as[LibraryService.Error]).sequence
|
_ <- UIO(assertEquals(res.map(_.status), Some(Status.NotFound)))
|
||||||
|
body <- res.traverse(_.as[LibraryService.Error])
|
||||||
_ <- UIO(
|
_ <- UIO(
|
||||||
assertEquals(
|
assertEquals(
|
||||||
body,
|
body,
|
||||||
@ -98,10 +101,10 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
// logger2 = logger.withConstContext(
|
// logger2 = logger.withConstContext(
|
||||||
// Map("Test" -> "get books by author name")
|
// Map("Test" -> "get books by author name")
|
||||||
// )
|
// )
|
||||||
routes = Http4sdemoRoutes.libraryRoutes(service)
|
routes = new LibraryRoutes(service, noopLogger).routes
|
||||||
request = Request[Task](
|
request = Request[Task](
|
||||||
Method.GET,
|
Method.GET,
|
||||||
Root / "api" / "get" / "book"
|
Root / "api" / "books"
|
||||||
withQueryParams Map(
|
withQueryParams Map(
|
||||||
"mode" -> BookSearchMode.AuthorName.entryName,
|
"mode" -> BookSearchMode.AuthorName.entryName,
|
||||||
"value" -> "blah"
|
"value" -> "blah"
|
||||||
@ -109,7 +112,7 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
)
|
)
|
||||||
// _ <- logger2.info(s"Request -> $request")
|
// _ <- logger2.info(s"Request -> $request")
|
||||||
res <- routes.run(request).value.hideErrors
|
res <- routes.run(request).value.hideErrors
|
||||||
body <- res.map(_.as[List[Book]]).sequence
|
body <- res.traverse(_.as[List[Book]])
|
||||||
_ <- UIO.pure(body).assertEquals(Some(books))
|
_ <- UIO.pure(body).assertEquals(Some(books))
|
||||||
// _ <- logger2.debug(s"Response body -> $body").hideErrors
|
// _ <- logger2.debug(s"Response body -> $body").hideErrors
|
||||||
} yield ()
|
} yield ()
|
||||||
@ -134,10 +137,10 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
// logger2 = logger.withConstContext(
|
// logger2 = logger.withConstContext(
|
||||||
// Map("Test" -> "get books by book title")
|
// Map("Test" -> "get books by book title")
|
||||||
// )
|
// )
|
||||||
routes = Http4sdemoRoutes.libraryRoutes(service)
|
routes = new LibraryRoutes(service, noopLogger).routes
|
||||||
request = Request[Task](
|
request = Request[Task](
|
||||||
Method.GET,
|
Method.GET,
|
||||||
Root / "api" / "get" / "book"
|
Root / "api" / "books"
|
||||||
withQueryParams Map(
|
withQueryParams Map(
|
||||||
"mode" -> BookSearchMode.BookTitle.entryName,
|
"mode" -> BookSearchMode.BookTitle.entryName,
|
||||||
"value" -> "blah"
|
"value" -> "blah"
|
||||||
@ -145,7 +148,7 @@ class LibraryControllerSpec extends MonixBioSuite {
|
|||||||
)
|
)
|
||||||
// _ <- logger2.info(s"Request -> $request")
|
// _ <- logger2.info(s"Request -> $request")
|
||||||
res <- routes.run(request).value.hideErrors
|
res <- routes.run(request).value.hideErrors
|
||||||
body <- res.map(_.as[List[Book]]).sequence
|
body <- res.traverse(_.as[List[Book]])
|
||||||
_ <- UIO.pure(body).assertEquals(Some(books))
|
_ <- UIO.pure(body).assertEquals(Some(books))
|
||||||
// _ <- logger2.debug(s"Response body -> $body").hideErrors
|
// _ <- logger2.debug(s"Response body -> $body").hideErrors
|
||||||
} yield ()
|
} yield ()
|
||||||
|
Loading…
Reference in New Issue
Block a user