Skip to content

Commit

Permalink
Port IRT Runtime to Scala 3; use Match Types as type projections work…
Browse files Browse the repository at this point in the history
…around to minimize changes and support Http4sContext type scala/scala3#13416
  • Loading branch information
neko-kai committed Mar 16, 2023
1 parent 7f96e35 commit 49c7b66
Show file tree
Hide file tree
Showing 22 changed files with 331 additions and 171 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ lazy val `idealingua-v1-runtime-rpc-scala` = project.in(file("idealingua-v1/idea
"io.circe" %% "circe-parser" % Izumi.Deps.fundamentals_json_circeJVM.io_circe_circe_core_version,
"io.circe" %% "circe-literal" % Izumi.Deps.fundamentals_json_circeJVM.io_circe_circe_core_version,
"dev.zio" %% "zio" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_version % Test,
"dev.zio" %% "zio-interop-cats" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_interop_cats_version % Test
"dev.zio" %% "zio-interop-cats" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_interop_cats_version % Test,
"dev.zio" %% "izumi-reflect" % Izumi.Deps.fundamentals_bioJVM.dev_zio_izumi_reflect_version % Test
),
libraryDependencies ++= { if (scalaVersion.value.startsWith("2.")) Seq(
compilerPlugin("org.typelevel" % "kind-projector" % V.kind_projector cross CrossVersion.full),
Expand Down Expand Up @@ -777,7 +778,8 @@ lazy val `idealingua-v1-test-defs` = project.in(file("idealingua-v1/idealingua-v
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % V.scalatest % Test,
"dev.zio" %% "zio" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_version,
"dev.zio" %% "zio-interop-cats" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_interop_cats_version
"dev.zio" %% "zio-interop-cats" % Izumi.Deps.fundamentals_bioJVM.dev_zio_zio_interop_cats_version,
"dev.zio" %% "izumi-reflect" % Izumi.Deps.fundamentals_bioJVM.dev_zio_izumi_reflect_version
),
libraryDependencies ++= { if (scalaVersion.value.startsWith("2.")) Seq(
compilerPlugin("org.typelevel" % "kind-projector" % V.kind_projector cross CrossVersion.full)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package izumi.idealingua.model.publishing

import izumi.fundamentals.platform.build.MacroParameters
import izumi.idealingua.`macro`.ProjectAttributeMacro
import izumi.idealingua.model.publishing.BuildManifest._

trait BuildManifest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package izumi.idealingua.model.common

sealed trait StreamDirection {

}
sealed trait StreamDirection

object StreamDirection {
final case object ToServer extends StreamDirection
final case object ToClient extends StreamDirection
case object ToServer extends StreamDirection
case object ToClient extends StreamDirection
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package izumi.idealingua.runtime.rpc

package object http4s {

/**
* This is a workaround that replaces usages of type projections with Match Types on Scala 3.
*
* See: https://github.com/lampepfl/dotty/issues/13416
*/
type GetBiIO[C <: Http4sContext] = { type l[+E, +A] = C#BiIO[E, A] }
type GetMonoIO[C <: Http4sContext] = { type l[+A] = C#BiIO[Throwable, A] }

type GetMaterializedStream[C <: Http4sContext] = C#MaterializedStream
type GetRequestContext[C <: Http4sContext] = C#RequestContext
type GetClientContext[C <: Http4sContext] = C#ClientContext
type GetMethodContext[C <: Http4sContext] = C#MethodContext
type GetClientMethodContext[C <: Http4sContext] = C#ClientMethodContext
type GetClientId[C <: Http4sContext] = C#ClientId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package izumi.idealingua.runtime.rpc

package object http4s {

/**
* This is a workaround that replaces usages of type projections with Match Types on Scala 3.
*
* See: https://github.com/lampepfl/dotty/issues/13416
*/
type GetBiIO[C <: Http4sContext] = { type l[+E, +A] = C match {
case GetBiIOPattern[f] => f[E, A]
} }
type GetMonoIO[C <: Http4sContext] = { type l[+A] = C match {
case GetBiIOPattern[f] => f[Throwable, A]
} }
type GetMaterializedStream[C <: Http4sContext] = C match {case GetMaterializedStreamPattern[t] => t}
type GetRequestContext[C <: Http4sContext] = C match {case GetRequestContextPattern[t] => t}
type GetClientContext[C <: Http4sContext] = C match {case GetClientContextPattern[t] => t}
type GetMethodContext[C <: Http4sContext] = C match {case GetMethodContextPattern[t] => t}
type GetClientMethodContext[C <: Http4sContext] = C match {case GetClientMethodContextPattern[t] => t}
type GetClientId[C <: Http4sContext] = C match {case GetClientIdPattern[t] => t}

type GetBiIOPattern[F[+_, +_]] = Http4sContext { type BiIO[+E, +A] = F[E, A] }
type GetMaterializedStreamPattern[T] = Http4sContext { type MaterializedStream = T }
type GetRequestContextPattern[T] = Http4sContext { type RequestContext = T }
type GetClientContextPattern[T] = Http4sContext { type ClientContext = T }
type GetMethodContextPattern[T] = Http4sContext { type MethodContext = T }
type GetClientMethodContextPattern[T] = Http4sContext { type ClientMethodContext = T }
type GetClientIdPattern[T] = Http4sContext { type ClientId = T }

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import org.http4s.blaze.client._

class ClientDispatcher[C <: Http4sContext]
(
val c: C#IMPL[C]
val c: Http4sContextImpl[C]
, logger: IzLogger
, printer: circe.Printer
, baseUri: Uri
, codec: IRTClientMultiplexor[C#BiIO]
) extends IRTDispatcher[C#BiIO] {
, codec: IRTClientMultiplexor[GetBiIO[C]#l]
) extends IRTDispatcher[GetBiIO[C]#l] {
import c._

def dispatch(request: IRTMuxRequest): BiIO[Throwable, IRTMuxResponse] = {
Expand Down Expand Up @@ -58,7 +58,7 @@ class ClientDispatcher[C <: Http4sContext]
body =>
logger.trace(s"${input.method -> "method"}: Received response: $body")
val decoded = for {
parsed <- c.F.fromEither(parse(body))
parsed <- F.fromEither(parse(body))
product <- codec.decode(parsed, input.method)
} yield {
logger.trace(s"${input.method -> "method"}: decoded response: $product")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,40 @@ package izumi.idealingua.runtime.rpc.http4s

import cats.effect.Async
import izumi.functional.bio.{IO2, Temporal2, UnsafeRun2}
import org.http4s.*
import org.http4s.dsl.*

import scala.concurrent.ExecutionContext

/**
* This is a workaround that replaces usages of type projections with Match Types on Scala 3.
*
* See: https://github.com/lampepfl/dotty/issues/13416
*/
trait Http4sContextImpl[C <: Http4sContext] {
final type BiIO[+E, +A] = GetBiIO[C]#l[E, A]
final type MonoIO[+A] = GetBiIO[C]#l[Throwable, A]

type MaterializedStream = GetMaterializedStream[C]
type RequestContext = GetRequestContext[C]
type MethodContext = GetMethodContext[C]
type ClientContext = GetClientContext[C]
type ClientMethodContext = GetClientMethodContext[C]
type ClientId = GetClientId[C]

implicit def F: IO2[GetBiIO[C]#l]
implicit def FT: Temporal2[GetBiIO[C]#l]
implicit def CIO: Async[MonoIO]

def UnsafeRun2: UnsafeRun2[BiIO]
val dsl: Http4sDsl[MonoIO]

def clientExecutionContext: ExecutionContext

final type DECL = C

final def self: Http4sContextImpl[DECL] = this
}

trait Http4sContext { outer =>
type BiIO[+E, +A]
final type MonoIO[+A] = BiIO[Throwable, A]
Expand All @@ -23,20 +52,20 @@ trait Http4sContext { outer =>

type ClientId

type StreamDecoder = EntityDecoder[MonoIO, MaterializedStream]
// type StreamDecoder = EntityDecoder[MonoIO, MaterializedStream]

implicit def F: IO2[BiIO]
implicit def FT: Temporal2[BiIO]
implicit def CIO: Async[MonoIO]
// implicit def F: IO2[BiIO]
// implicit def FT: Temporal2[BiIO]
// implicit def CIO: Async[MonoIO]

def UnsafeRun2: UnsafeRun2[BiIO]
val dsl: Http4sDsl[MonoIO]

def clientExecutionContext: ExecutionContext
// def UnsafeRun2: UnsafeRun2[BiIO]
// val dsl: Http4sDsl[MonoIO]

final type DECL = this.type
// def clientExecutionContext: ExecutionContext

final def self: IMPL[DECL] = this
// final type DECL = this.type
//
// final def self: IMPL[DECL] = this

type Aux[_BiIO[+_, +_], _RequestContext, _MethodContext, _ClientId, _ClientContext, _ClientMethodContext] = Http4sContext {
type BiIO[+E, +V] = _BiIO[E, V]
Expand All @@ -47,13 +76,13 @@ trait Http4sContext { outer =>
type ClientId = _ClientId
}

/**
* This is to prove type equality between a type `C <: Http4sContext` and `c: C`
* Scalac treats them as different, even when there's a Singleton upper bound!
*
* @see details: https://gist.github.com/pshirshov/1273add00d902a6cfebd72426d7ed758
* @see dotty: https://github.com/lampepfl/dotty/issues/4583#issuecomment-435382992
* @see intellij highlighting fixed: https://youtrack.jetbrains.net/issue/SCL-14533
*/
final type IMPL[C <: Http4sContext] = Aux[C#BiIO, C#RequestContext, C#MethodContext, C#ClientId, C#ClientContext, C#ClientMethodContext]
// /**
// * This is to prove type equality between a type `C <: Http4sContext` and `c: C`
// * Scalac treats them as different, even when there's a Singleton upper bound!
// *
// * @see details: https://gist.github.com/pshirshov/1273add00d902a6cfebd72426d7ed758
// * @see dotty: https://github.com/lampepfl/dotty/issues/4583#issuecomment-435382992
// * @see intellij highlighting fixed: https://youtrack.jetbrains.net/issue/SCL-14533
// */
// final type IMPL[C <: Http4sContext] = Aux[C#BiIO, C#RequestContext, C#MethodContext, GetClientId[C], C#ClientContext, C#ClientMethodContext]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,7 @@ class Http4sRuntime[
override val clientExecutionContext: ExecutionContext
)(implicit
C: Async[_BiIO[Throwable, _]]
) extends Http4sContext {

override type BiIO[+E, +V] = _BiIO[E, V]
override type RequestContext = _RequestContext
override type MethodContext = _MethodContext
override type ClientId = _ClientId
override type ClientContext = _ClientContext
override type ClientMethodContext = _ClientMethodContext

) extends Http4sContextImpl[Http4sContext#Aux[_BiIO, _RequestContext, _MethodContext, _ClientId, _ClientContext, _ClientMethodContext]] {
override val F: IO2[BiIO] = implicitly
override val FT: Temporal2[BiIO] = implicitly
override val CIO: Async[MonoIO] = implicitly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import java.time.ZonedDateTime
import java.util.concurrent.RejectedExecutionException

class HttpServer[C <: Http4sContext](
val c: C#IMPL[C],
val muxer: IRTServerMultiplexor[C#BiIO, C#RequestContext],
val codec: IRTClientMultiplexor[C#BiIO],
val contextProvider: AuthMiddleware[C#MonoIO, C#RequestContext],
val wsContextProvider: WsContextProvider[C#BiIO, C#RequestContext, C#ClientId],
val wsSessionStorage: WsSessionsStorage[C#BiIO, C#ClientId, C#RequestContext],
val listeners: Seq[WsSessionListener[C#BiIO, C#ClientId]],
val c: Http4sContextImpl[C],
val muxer: IRTServerMultiplexor[GetBiIO[C]#l, GetRequestContext[C]],
val codec: IRTClientMultiplexor[GetBiIO[C]#l],
val contextProvider: AuthMiddleware[GetMonoIO[C]#l, GetRequestContext[C]],
val wsContextProvider: WsContextProvider[GetBiIO[C]#l, GetRequestContext[C], GetClientId[C]],
val wsSessionStorage: WsSessionsStorage[GetBiIO[C]#l, GetClientId[C], GetRequestContext[C]],
val listeners: Seq[WsSessionListener[GetBiIO[C]#l, GetClientId[C]]],
logger: IzLogger,
printer: Printer
) {
Expand Down Expand Up @@ -82,28 +82,28 @@ class HttpServer[C <: Http4sContext](
}
}

protected def handleWsClose(context: WebsocketClientContext[C#BiIO, C#ClientId, C#RequestContext]): MonoIO[Unit] = {
protected def handleWsClose(context: WebsocketClientContext[BiIO, ClientId, RequestContext]): MonoIO[Unit] = {
F.sync(logger.debug(s"${context -> null}: Websocket client disconnected")) *>
context.finish()
}

protected def onWsOpened(): MonoIO[Unit] = F.unit

protected def onWsUpdate(maybeNewId: Option[C#ClientId], old: WsClientId[ClientId]): MonoIO[Unit] = F.sync {
protected def onWsUpdate(maybeNewId: Option[ClientId], old: WsClientId[ClientId]): MonoIO[Unit] = F.sync {
(maybeNewId, old).forget
}

protected def onWsClosed(): MonoIO[Unit] = F.unit

protected def setupWs(request: AuthedRequest[MonoIO, RequestContext], initialContext: RequestContext, ws: WebSocketBuilder2[MonoIO]): MonoIO[Response[MonoIO]] = {
val context = new WebsocketClientContextImpl[C](c, request, initialContext, listeners, wsSessionStorage, logger) {
override def onWsSessionOpened(): C#MonoIO[Unit] = {
override def onWsSessionOpened(): MonoIO[Unit] = {
onWsOpened() *> super.onWsSessionOpened()
}
override def onWsClientIdUpdate(maybeNewId: Option[C#ClientId], oldId: WsClientId[C#ClientId]): C#MonoIO[Unit] = {
override def onWsClientIdUpdate(maybeNewId: Option[ClientId], oldId: WsClientId[ClientId]): MonoIO[Unit] = {
onWsUpdate(maybeNewId, oldId) *> super.onWsClientIdUpdate(maybeNewId, oldId)
}
override def onWsSessionClosed(): C#MonoIO[Unit] = {
override def onWsSessionClosed(): MonoIO[Unit] = {
onWsClosed() *> super.onWsSessionClosed()
}
}
Expand Down Expand Up @@ -146,7 +146,7 @@ class HttpServer[C <: Http4sContext](
F.pure(None)
}

def onHeartbeat(requestTime: ZonedDateTime): C#MonoIO[Unit] = {
def onHeartbeat(requestTime: ZonedDateTime): MonoIO[Unit] = {
requestTime.discard()
F.unit
}
Expand Down Expand Up @@ -251,7 +251,7 @@ class HttpServer[C <: Http4sContext](
context: HttpRequestContext[MonoIO, RequestContext],
method: IRTMethodId,
result: Exit[Throwable, Option[Json]]
): C#MonoIO[Response[MonoIO]] = {
): MonoIO[Response[MonoIO]] = {
result match {
case Success(v) =>
v match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ trait WsClientContextProvider[Ctx] {
*/
class ClientWsDispatcher[C <: Http4sContext]
(
val c: C#IMPL[C],
val c: Http4sContextImpl[C],
protected val baseUri: URI,
protected val codec: IRTClientMultiplexor[C#BiIO],
protected val buzzerMuxer: IRTServerMultiplexor[C#BiIO, C#ClientContext],
protected val wsClientContextProvider: WsClientContextProvider[C#ClientContext],
protected val codec: IRTClientMultiplexor[GetBiIO[C]#l],
protected val buzzerMuxer: IRTServerMultiplexor[GetBiIO[C]#l, GetClientContext[C]],
protected val wsClientContextProvider: WsClientContextProvider[GetClientContext[C]],
logger: IzLogger,
printer: Printer,
) extends IRTDispatcher[C#BiIO] with AutoCloseable {
) extends IRTDispatcher[GetBiIO[C]#l] with AutoCloseable {

import c._
import c.*

val requestState = new RequestState[BiIO]()

import org.asynchttpclient.Dsl._
import org.asynchttpclient.Dsl.*

private val wsc = asyncHttpClient(config())
private val listener = new WebSocketListener() {
Expand All @@ -66,7 +66,7 @@ class ClientWsDispatcher[C <: Http4sContext]
private val connection = new AtomicReference[NettyWebSocket]()

private def send(out: String): Unit = {
import scala.jdk.CollectionConverters._
import scala.jdk.CollectionConverters.*
connection.synchronized {
if (connection.get() == null) {
connection.set {
Expand Down Expand Up @@ -171,7 +171,7 @@ class ClientWsDispatcher[C <: Http4sContext]
}


import scala.concurrent.duration._
import scala.concurrent.duration.*

protected val timeout: FiniteDuration = 2.seconds
protected val pollingInterval: FiniteDuration = 50.millis
Expand Down
Loading

0 comments on commit 49c7b66

Please sign in to comment.