Skip to content

Commit

Permalink
Update Cats Effect to 3.6
Browse files Browse the repository at this point in the history
- Drop scala-native-loop and epollcat
- Drop support for Future and AsyncServerBuilder
  • Loading branch information
lolgab committed May 1, 2024
1 parent 0c74c67 commit 61987a0
Show file tree
Hide file tree
Showing 21 changed files with 169 additions and 299 deletions.
2 changes: 1 addition & 1 deletion .mill-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.11.6
0.11.7-70-654f58
66 changes: 28 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

```scala
import snunit.*
object HelloWorldExample {
def main(args: Array[String]): Unit = {
SyncServerBuilder
.setRequestHandler(req =>
req.send(
statusCode = StatusCode.OK,
content = "Hello world!\n",
headers = Headers("Content-Type" -> "text/plain")
)

@main
def run =
SyncServerBuilder
.setRequestHandler(req =>
req.send(
statusCode = StatusCode.OK,
content = "Hello world!\n",
headers = Headers("Content-Type" -> "text/plain")
)
.build()
.listen()
}
}
)
.build()
.listen()
```

SNUnit is a Scala Native library to write HTTP server applications on top of
Expand Down Expand Up @@ -96,32 +95,26 @@ anything else while listening.
Moreover, all the request handlers need to respond directly and can't be implemented
using `Future`s or any other asyncronous mechanism since no `Future` will run, being
the process stuck on the `listen()` Unit event loop.
With `AsyncServerBuilder` the server is automatically scheduled to run either on the
[scala-native-loop](https://github.com/scala-native/scala-native-loop) event loop
(based on the libuv library) or [epollcat](https://github.com/armanbilge/epollcat) event
loop, based on epoll/kqueue.
With http4s or tapir-cats-effect the server is automatically scheduled to run either on the
cats effect event loop, based on epoll/kqueue.
This allows you to complete requests asyncronously using whatever mechanism you prefer.
A process can accept multiple requests concurrently, allowing great parallelism.
Add either `snunit-async-loop` or `snunit-async-epollcat` to decide what implementation
you want to use.

## Tapir support

SNUnit offers interpreters for [Tapir](https://tapir.softwaremill.com) server endpoints.
You can write all your application using Tapir and the convert your Tapir endpoints
with logic into a SNUnit `Handler`.

Currently three interpreters are available:
Currently two interpreters are available:
- `SNUnitIdServerInterpreter` which works best with `SyncServerHandler` for synchronous applications
- You can find an example [in tests](./integration/tests/tapir-helloworld/src/Main.scala)
- `SNUnitFutureServerInterpreter` which requires `AsyncServerHandler` for asynchronous applications
- You can find an example [in tests](./integration/tests/tapir-helloworld-future/src/Main.scala)
- An interpreter for cats hidden behind `snunit.tapir.SNUnitServerBuilder` in the `snunit-tapir-cats-effect` artifact.
- You can find an example [in tests](./integration/tests/tapir-helloworld-cats-effect/src/Main.scala)

### Automatic server creation

`snunit.TapirApp` extends `epollcat.EpollApp` building the SNUnit server.
`snunit.TapirApp` extends `cats.effect.IOApp` building the SNUnit server.

It exposes a `def serverEndpoints: Resource[IO, List[ServerEndpoint[Any, IO]]]` that you need to
implement with your server logic.
Expand Down Expand Up @@ -152,17 +145,17 @@ There are two ways you can build a http4s server.

### Automatic server creation

`snunit.Http4sApp` extends `epollcat.EpollApp` building the SNUnit server.
`snunit.Http4sApp` extends `cats.effect.IOApp` building the SNUnit server.

It exposes a `def routes: Resource[IO, HttpApp[IO]]` that you need to implement with your
server logic.

Here an example "Hello world" app:

```scala
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
import cats.effect.*
import org.http4s.*
import org.http4s.dsl.io.*

object app extends snunit.Http4sApp {
def routes = Resource.pure(
Expand All @@ -180,29 +173,26 @@ object app extends snunit.Http4sApp {
If you want to have more control over the server creation, you can use the
`SNUnitServerBuilder` and manually use it.

For example, here you see it in combination with `epollcat.EpollApp`
For example, here you see it in combination with `cats.effect.IOApp`

```scala
package snunit.tests

import cats.effect._
import epollcat.EpollApp
import org.http4s._
import org.http4s.dsl.io._
import snunit.http4s._
import cats.effect.*
import org.http4s.*
import org.http4s.dsl.io.*
import snunit.http4s.*

object Http4sHelloWorld extends EpollApp.Simple {
def helloWorldRoutes: HttpRoutes[IO] = {
object Http4sHelloWorld extends IOApp.Simple {
def helloWorldRoutes: HttpRoutes[IO] =
HttpRoutes.of[IO] { case GET -> Root =>
Ok("Hello Http4s!")
}
}

def run: IO[Unit] = {
def run: IO[Unit] =
SNUnitServerBuilder
.default[IO]
.withHttpApp(helloWorldRoutes.orNotFound)
.run
}
}
```
52 changes: 12 additions & 40 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -97,35 +97,21 @@ trait Publish extends CiReleaseModule with Mima {
)
def mimaPreviousVersions = Seq("0.8.0")
}
object `snunit-internal-api` extends JavaModule
object snunit extends Cross[SNUnitModule](scalaVersions)
trait SNUnitModule extends Common.Cross with Publish {
def compileModuleDeps = Seq(`snunit-internal-api`)
object test extends ScalaNativeTests with TestModule.Utest {
def ivyDeps = super.ivyDeps() ++ Agg(utest)
}
}

object `snunit-async-loop` extends Cross[SNUnitAsyncModule](scalaVersions)
trait SNUnitAsyncModule extends Common.Cross with Publish {
object `snunit-async-cats-effect` extends Cross[SNUnitAsyncCatsEffectModule](scalaVersions)
trait SNUnitAsyncCatsEffectModule extends Common.Cross with Publish {
def moduleDeps = Seq(snunit())

def ivyDeps =
T {
super.ivyDeps() ++ Agg(
ivy"com.github.lolgab::native-loop-core::${Versions.scalaNativeLoop}"
)
}
}

object `snunit-async-epollcat` extends Cross[SNUnitAsyncEpollcatModule](scalaVersions)
trait SNUnitAsyncEpollcatModule extends Common.Cross with Publish {
def moduleDeps = Seq(snunit())

def ivyDeps =
T {
super.ivyDeps() ++ Agg(
ivy"com.armanbilge::epollcat::${Versions.epollcat}"
ivy"org.typelevel::cats-effect::${Versions.catsEffect}"
)
}
}
Expand All @@ -143,8 +129,8 @@ trait SNUnitTapirModule extends Common.Cross with Publish {
object `snunit-tapir-cats-effect` extends Cross[SNUnitTapirCatsEffect](scalaVersions)
trait SNUnitTapirCatsEffect extends Common.Cross with Publish {
def moduleDeps = Seq(
`snunit-tapir`(crossScalaVersion),
`snunit-async-epollcat`(crossScalaVersion)
`snunit-tapir`(),
`snunit-async-cats-effect`()
)
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"com.softwaremill.sttp.tapir::tapir-cats-effect::${Versions.tapir}"
Expand All @@ -156,14 +142,16 @@ trait SNUnitHttp4s extends Common.Cross with Cross.Module2[String, String] with
val http4sVersion = crossValue2
def moduleDeps = Seq(
snunit(),
`snunit-async-epollcat`(crossScalaVersion)
`snunit-async-cats-effect`()
)
val http4sBinaryVersion = http4sVersion match {
case s"0.23.$_" => "0.23"
case s"1.$_" => "1"
}
def artifactName = s"snunit-http4s$http4sBinaryVersion"
def ivyDeps = super.ivyDeps() ++ Agg(ivy"org.http4s::http4s-server::$http4sVersion")
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.http4s::http4s-server::$http4sVersion"
)
def sources = T.sources {
super.sources() ++ Agg(PathRef(millSourcePath / s"http4s-$http4sBinaryVersion" / "src"))
}
Expand Down Expand Up @@ -206,15 +194,6 @@ object integration extends ScalaModule {
object `multiple-handlers` extends Common.Scala3Only {
def moduleDeps = Seq(snunit(crossScalaVersion))
}
object async extends Common.Scala3Only {
def moduleDeps = Seq(`snunit-async-loop`(crossScalaVersion))
}
object `async-epollcat` extends Common.Scala3Only {
def moduleDeps = Seq(`snunit-async-epollcat`(crossScalaVersion))
}
object `async-multiple-handlers` extends Common.Scala3Only {
def moduleDeps = Seq(`snunit-async-loop`(crossScalaVersion))
}
object `undertow-helloworld` extends CrossPlatform {
object native extends CrossPlatformScalaModule with Common.Scala3Only {
def moduleDeps = Seq(`snunit-undertow`(crossScalaVersion))
Expand Down Expand Up @@ -258,12 +237,6 @@ object integration extends ScalaModule {
ivy"org.http4s::http4s-dsl::$http4sVersion"
)
}
object `tapir-helloworld-future` extends Common.Scala3Only {
def moduleDeps = Seq(
`snunit-async-loop`(crossScalaVersion),
`snunit-tapir`(crossScalaVersion)
)
}
object `tapir-helloworld-cats-effect` extends Common.Scala3Only {
def moduleDeps = Seq(
`snunit-tapir-cats-effect`(crossScalaVersion)
Expand All @@ -276,7 +249,6 @@ object integration extends ScalaModule {
BuildInfo.Value("port", testServerPort.toString),
BuildInfo.Value("scalaVersions", scalaVersions.mkString(":")),
BuildInfo.Value("http4sVersions", http4sVersions.mkString(":")),
BuildInfo.Value("scala213", Versions.scala213),
BuildInfo.Value("unitControl", unitd.control.toString)
)
def buildInfoPackageName = "snunit.test"
Expand All @@ -303,15 +275,15 @@ trait SnunitPluginsShared extends CrossScalaModule with Publish with BuildInfo {
}
}
object `snunit-mill-plugin` extends ScalaModule with Publish {
def artifactName = s"mill-snunit_mill${Versions.mill010.split('.').take(2).mkString(".")}"
def artifactName = s"mill-snunit_mill${Versions.mill011.split('.').take(2).mkString(".")}"
def moduleDeps = Seq(`snunit-plugins-shared`(Versions.scala213))
def scalaVersion = Versions.scala213
def compileIvyDeps = super.compileIvyDeps() ++ Agg(
ivy"com.lihaoyi::mill-scalanativelib:${Versions.mill010}"
ivy"com.lihaoyi::mill-scalanativelib:${Versions.mill011}"
)
}
object `snunit-mill-plugin-itest` extends MillIntegrationTestModule {
def millTestVersion = Versions.mill010
def millTestVersion = Versions.mill011
def pluginsUnderTest = Seq(`snunit-mill-plugin`)
def temporaryIvyModules = Seq(`snunit-plugins-shared`(Versions.scala213), snunit(Versions.scala3))
}
Expand Down
11 changes: 5 additions & 6 deletions integration/tests/http4s-helloworld/src/Main.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package snunit.tests

import cats.effect._
import epollcat.EpollApp
import org.http4s._
import org.http4s.dsl.io._
import snunit.http4s._
import cats.effect.*
import org.http4s.*
import org.http4s.dsl.io.*
import snunit.http4s.*

object Http4sHelloWorld extends EpollApp.Simple {
object Http4sHelloWorld extends IOApp.Simple {
def helloWorldRoutes: HttpRoutes[IO] = {
HttpRoutes.of[IO] { case GET -> Root =>
Ok("Hello Http4s!")
Expand Down
8 changes: 4 additions & 4 deletions integration/tests/tapir-helloworld-cats-effect/src/Main.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package snunit.tests

import cats.effect._
import snunit.tapir._
import sttp.tapir._
import cats.effect.*
import snunit.tapir.*
import sttp.tapir.*

object TapirHelloWorldIO extends epollcat.EpollApp.Simple {
object TapirHelloWorldIO extends IOApp.Simple {
val helloWorld = endpoint.get
.in("hello")
.in(query[String]("name"))
Expand Down
19 changes: 0 additions & 19 deletions integration/tests/tapir-helloworld-future/src/Main.scala

This file was deleted.

Loading

0 comments on commit 61987a0

Please sign in to comment.