-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: move from Scala2 to Scala3 (#6)
* feat(api): add * feat(api): separate server creation * feat(all): add `http4s` & introduce `scala3` fixes
- Loading branch information
1 parent
2f45484
commit 4e13b81
Showing
11 changed files
with
200 additions
and
133 deletions.
There are no files selected for viewing
This file contains 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 characters
This file contains 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 characters
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,29 @@ | ||
import sbt._ | ||
|
||
object Dependencies { | ||
lazy val akkaVersion = "2.6.20" | ||
lazy val akkaHttpVersion = "10.2.10" | ||
lazy val akkaVersion = "2.7.0" | ||
lazy val akkaHttpVersion = "10.4.0" | ||
lazy val scalaTestVersion = "3.2.15" | ||
lazy val scalaLogVersion = "1.2.11" | ||
lazy val sprayVersion = "1.3.6" | ||
lazy val dockerVersion = "8.9.0" | ||
lazy val catsEffectVersion = "3.3.11" | ||
lazy val circeVersion = "0.14.5" | ||
lazy val pureconfigVersion = "0.17.1" | ||
lazy val http4sVersion = "0.23.29" | ||
|
||
lazy val akkaActor = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion | ||
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % akkaVersion | ||
lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % akkaHttpVersion | ||
lazy val scalaLogging = "ch.qos.logback" % "logback-classic" % scalaLogVersion | ||
lazy val spray = "io.spray" %% "spray-json" % sprayVersion | ||
lazy val docker = "com.spotify" % "docker-client" % dockerVersion | ||
lazy val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion | ||
lazy val circeCore = "io.circe" %% "circe-core" % circeVersion | ||
lazy val circeGeneric = "io.circe" %% "circe-generic" % circeVersion | ||
lazy val circeParser = "io.circe" %% "circe-parser" % circeVersion | ||
lazy val pureconfig = "com.github.pureconfig" %% "pureconfig" % pureconfigVersion | ||
lazy val akkaTest = "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test | ||
lazy val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion % Test | ||
lazy val akkaActor = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion | ||
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % akkaVersion | ||
lazy val scalaLogging = "ch.qos.logback" % "logback-classic" % scalaLogVersion | ||
lazy val docker = "com.spotify" % "docker-client" % dockerVersion | ||
lazy val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion | ||
lazy val circeCore = "io.circe" %% "circe-core" % circeVersion | ||
lazy val circeGeneric = "io.circe" %% "circe-generic" % circeVersion | ||
lazy val circeParser = "io.circe" %% "circe-parser" % circeVersion | ||
lazy val pureconfig = "com.github.pureconfig" %% "pureconfig" % pureconfigVersion | ||
lazy val http4sEmberClient = "org.http4s" %% "http4s-ember-client" % http4sVersion | ||
lazy val http4sEmberServer = "org.http4s" %% "http4s-ember-server" % http4sVersion | ||
lazy val http4sCirce = "org.http4s" %% "http4s-circe" % http4sVersion | ||
lazy val http4sDSL = "org.http4s" %% "http4s-dsl" % http4sVersion | ||
lazy val akkaTest = "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test | ||
lazy val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion % Test | ||
} |
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") | ||
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.4") | ||
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") | ||
addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.17.5") | ||
resolvers += "Scalafmt Releases" at "https://oss.sonatype.org/content/repositories/releases" |
This file contains 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 characters
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,41 @@ | ||
package api | ||
|
||
import akka.actor.ActorSystem | ||
import cats.effect.{IO, Resource} | ||
import akka.http.scaladsl.Http | ||
import akka.http.scaladsl.server.Directives._ | ||
import akka.http.scaladsl.server.Route | ||
package resource.api | ||
|
||
import cats.effect.IO | ||
import cats.syntax.semigroupk.toSemigroupKOps | ||
import org.http4s.HttpRoutes | ||
import org.http4s.dsl.io._ | ||
import org.http4s.server.Router | ||
import org.http4s.circe.CirceEntityEncoder._ | ||
import org.slf4j.LoggerFactory | ||
|
||
class Endpoints { | ||
private val logger = LoggerFactory.getLogger(getClass) | ||
|
||
private def healthRoute: Route = | ||
path("health")(get { | ||
private val healthRoute: HttpRoutes[IO] = HttpRoutes.of[IO] { | ||
case GET -> Root / "health" => | ||
logger.info("[Request]: get health check") | ||
complete("Health check response") | ||
}) | ||
Ok("Health check response") | ||
} | ||
|
||
private def getReactionRoute: Route = | ||
path("reaction" / Segment) { id => | ||
get { | ||
logger.info(s"[Request]: get reaction details for ID: $id") | ||
complete(s"Get reaction details for ID: $id") | ||
} | ||
} | ||
private val getReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] { | ||
case GET -> Root / "reaction" / id => | ||
logger.info(s"[Request]: get reaction details for ID: $id") | ||
Ok(s"Get reaction details for ID: $id") | ||
} | ||
|
||
private def postReactionRoute: Route = | ||
path("reaction")(post { | ||
private val postReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] { | ||
case POST -> Root / "reaction" => | ||
logger.info("[Request]: create new reaction") | ||
complete("Create new reaction") | ||
}) | ||
|
||
private def deleteReactionRoute: Route = | ||
path("reaction" / Segment) { id => | ||
delete { | ||
logger.info(s"[Request]: delete reaction with ID: $id") | ||
complete(s"Delete reaction with ID: $id") | ||
} | ||
} | ||
|
||
private val routes: Route = | ||
pathPrefix("api") { | ||
healthRoute ~ | ||
getReactionRoute ~ | ||
postReactionRoute ~ | ||
deleteReactionRoute | ||
} | ||
Ok("Create new reaction") | ||
} | ||
|
||
def startServer( | ||
interface: String, | ||
port: Int | ||
)( | ||
implicit system: ActorSystem | ||
): Resource[IO, Http.ServerBinding] = { | ||
Resource.make { | ||
IO.fromFuture(IO(Http().newServerAt(interface, port).bind(routes))) | ||
.flatTap { binding => | ||
IO( | ||
logger.info( | ||
s"Server online at http://${binding.localAddress.getHostName}:${binding.localAddress.getPort}/" + | ||
"\nPress ENTER to terminate..." | ||
) | ||
) | ||
} | ||
} { binding => | ||
IO.fromFuture(IO(binding.unbind)) | ||
.flatTap(_ => IO(logger.info("Server unbound successfully"))) | ||
.handleErrorWith(ex => IO(logger.error(s"Failed to unbind server: ${ex.getMessage}"))) | ||
.as(()) | ||
} | ||
private val deleteReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] { | ||
case DELETE -> Root / "reaction" / id => | ||
logger.info(s"[Request]: delete reaction with ID: $id") | ||
Ok(s"Delete reaction with ID: $id") | ||
} | ||
|
||
val routes: HttpRoutes[IO] = Router( | ||
"/api" -> (healthRoute <+> getReactionRoute <+> postReactionRoute <+> deleteReactionRoute) | ||
) | ||
} |
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package resource.api | ||
|
||
import cats.effect.IO | ||
import org.http4s._ | ||
import io.circe.syntax._ | ||
import io.circe.generic.auto._ | ||
import org.http4s.circe.CirceEntityEncoder._ | ||
|
||
final case class ErrorResponse(error: String, message: String) | ||
|
||
object ErrorHandler { | ||
def apply(routes: HttpRoutes[IO]): HttpRoutes[IO] = HttpRoutes.of[IO] { request => | ||
routes(request).getOrElseF( | ||
IO(Response(Status.NotFound).withEntity(ErrorResponse( | ||
"NotFound", | ||
"Resource not found" | ||
).asJson)) | ||
).handleErrorWith { | ||
case _: NoSuchElementException => | ||
IO.pure(Response(Status.NotFound).withEntity(ErrorResponse( | ||
"NotFound", | ||
"Resource not found" | ||
).asJson)) | ||
|
||
case ex: IllegalArgumentException => | ||
IO.pure(Response(Status.BadRequest).withEntity(ErrorResponse( | ||
"BadRequest", | ||
ex.getMessage | ||
).asJson)) | ||
|
||
case ex: Exception => | ||
IO.pure(Response(Status.InternalServerError).withEntity(ErrorResponse( | ||
"InternalServerError", | ||
"An unexpected error occurred." | ||
).asJson)) | ||
} | ||
} | ||
} |
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package resource.api | ||
|
||
import cats.effect.{IO, Resource} | ||
import com.comcast.ip4s.{Host, Port} | ||
import cats.syntax.flatMap.toFlatMapOps | ||
import org.http4s.server.Server | ||
import org.http4s.ember.server.EmberServerBuilder | ||
import org.slf4j.LoggerFactory | ||
|
||
class ServerBuilder( | ||
implicit endpoints: Endpoints | ||
) { | ||
private val logger = LoggerFactory.getLogger(getClass) | ||
|
||
def startServer( | ||
interface: Host, | ||
port: Port | ||
): Resource[IO, Server] = { | ||
EmberServerBuilder | ||
.default[IO] | ||
.withHost(interface) | ||
.withPort(port) | ||
.withHttpApp(endpoints.routes.orNotFound) | ||
.build | ||
.flatTap { server => | ||
Resource.eval(IO(logger.info( | ||
s"Server online at http://${server.address.getHostName}:${server.address.getPort}/" + | ||
s"\nPress ENTER to terminate..." | ||
))) | ||
} | ||
} | ||
} |
This file contains 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 characters
Oops, something went wrong.