Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ val commonSettings = Seq(
case Some((2, _)) => Seq(s"-target:jvm-$jdkVersion")
case _ => Seq(s"-java-output-version:$jdkVersion")
}
},
bspEnabled := {
val id = thisProjectRef.value.project
val isScala3 = scalaVersion.value.startsWith("3.")
val isJS = id.contains("JS")
val isNative = id.contains("Native")

isScala3 && !isJS && !isNative
}
)

Expand Down
30 changes: 20 additions & 10 deletions modules/core/src/main/scala/jsonrpclib/Endpoint.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package jsonrpclib

import io.circe.Codec
import jsonrpclib.ErrorCodec.errorPayloadCodec

sealed trait Endpoint[F[_]] {
def method: String

def mapK[G[_]](f: F ~> G): Endpoint[G]
}

object Endpoint {
Expand All @@ -16,20 +19,20 @@ object Endpoint {
class PartiallyAppliedEndpoint[F[_]](method: MethodPattern) {
def apply[In, Err, Out](
run: In => F[Either[Err, Out]]
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): Endpoint[F] =
RequestResponseEndpoint(method, (_: InputMessage, in: In) => run(in), inCodec, errCodec, outCodec)
)(implicit inCodec: Codec[In], errEncoder: ErrorEncoder[Err], outCodec: Codec[Out]): Endpoint[F] =
RequestResponseEndpoint(method, (_: InputMessage, in: In) => run(in), inCodec, errEncoder, outCodec)

def full[In, Err, Out](
run: (InputMessage, In) => F[Either[Err, Out]]
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): Endpoint[F] =
RequestResponseEndpoint(method, run, inCodec, errCodec, outCodec)
)(implicit inCodec: Codec[In], errEncoder: ErrorEncoder[Err], outCodec: Codec[Out]): Endpoint[F] =
RequestResponseEndpoint(method, run, inCodec, errEncoder, outCodec)

def simple[In, Out](
run: In => F[Out]
)(implicit F: Monadic[F], inCodec: Codec[In], outCodec: Codec[Out]) =
apply[In, ErrorPayload, Out](in =>
F.doFlatMap(F.doAttempt(run(in))) {
case Left(error) => F.doPure(Left(ErrorPayload(0, error.getMessage(), None)))
case Left(error) => F.doPure(Left(ErrorPayload(-32000, error.getMessage(), None)))
case Right(value) => F.doPure(Right(value))
}
)
Expand All @@ -42,18 +45,25 @@ object Endpoint {

}

final case class NotificationEndpoint[F[_], In](
private[jsonrpclib] final case class NotificationEndpoint[F[_], In](
method: MethodPattern,
run: (InputMessage, In) => F[Unit],
inCodec: Codec[In]
) extends Endpoint[F]
) extends Endpoint[F] {

def mapK[G[_]](f: F ~> G): Endpoint[G] =
NotificationEndpoint[G, In](method, (msg, in) => f(run(msg, in)), inCodec)
}

final case class RequestResponseEndpoint[F[_], In, Err, Out](
private[jsonrpclib] final case class RequestResponseEndpoint[F[_], In, Err, Out](
method: Method,
run: (InputMessage, In) => F[Either[Err, Out]],
inCodec: Codec[In],
errCodec: ErrorCodec[Err],
errEncoder: ErrorEncoder[Err],
outCodec: Codec[Out]
) extends Endpoint[F]
) extends Endpoint[F] {

def mapK[G[_]](f: F ~> G): Endpoint[G] =
RequestResponseEndpoint[G, In, Err, Out](method, (msg, in) => f(run(msg, in)), inCodec, errEncoder, outCodec)
}
}
9 changes: 6 additions & 3 deletions modules/core/src/main/scala/jsonrpclib/ErrorCodec.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package jsonrpclib

trait ErrorCodec[E] {

trait ErrorEncoder[E] {
def encode(a: E): ErrorPayload
def decode(error: ErrorPayload): Either[ProtocolError, E]
}

trait ErrorDecoder[E] {
def decode(error: ErrorPayload): Either[ProtocolError, E]
}

trait ErrorCodec[E] extends ErrorDecoder[E] with ErrorEncoder[E]

object ErrorCodec {
implicit val errorPayloadCodec: ErrorCodec[ErrorPayload] = new ErrorCodec[ErrorPayload] {
def encode(a: ErrorPayload): ErrorPayload = a
Expand Down
5 changes: 5 additions & 0 deletions modules/core/src/main/scala/jsonrpclib/PolyFunction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package jsonrpclib

private[jsonrpclib] trait PolyFunction[F[_], G[_]] { self =>
def apply[A0](fa: F[A0]): G[A0]
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.util.Try
import io.circe.Codec
import io.circe.Encoder

abstract class FutureBasedChannel(endpoints: List[Endpoint[Future]])(implicit ec: ExecutionContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F

def stub[In, Err, Out](
method: String
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): In => F[Either[Err, Out]] = {
)(implicit inCodec: Codec[In], errDecoder: ErrorDecoder[Err], outCodec: Codec[Out]): In => F[Either[Err, Out]] = {
(input: In) =>
val encoded = inCodec(input)
doFlatMap(nextCallId()) { callId =>
val message = InputMessage.RequestMessage(method, callId, Some(Payload(encoded)))
doFlatMap(createPromise[Either[Err, Out]](callId)) { case (fulfill, future) =>
val pc = createPendingCall(errCodec, outCodec, fulfill)
val pc = createPendingCall(errDecoder, outCodec, fulfill)
doFlatMap(storePendingCall(callId, pc))(_ => doFlatMap(sendMessage(message))(_ => future()))
}
}
Expand Down Expand Up @@ -85,7 +85,7 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F
val responseData = ep.outCodec(data)
sendMessage(OutputMessage.ResponseMessage(callId, Payload(responseData)))
case Left(error) =>
val errorPayload = ep.errCodec.encode(error)
val errorPayload = ep.errEncoder.encode(error)
sendMessage(OutputMessage.ErrorMessage(callId, errorPayload))
}
case Left(pError) =>
Expand All @@ -111,13 +111,13 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F
}

private def createPendingCall[Err, Out](
errCodec: ErrorCodec[Err],
errDecoder: ErrorDecoder[Err],
outCodec: Codec[Out],
fulfill: Try[Either[Err, Out]] => F[Unit]
): OutputMessage => F[Unit] = { (message: OutputMessage) =>
message match {
case ErrorMessage(_, errorPayload) =>
errCodec.decode(errorPayload) match {
errDecoder.decode(errorPayload) match {
case Left(_) => fulfill(scala.util.Failure(errorPayload))
case Right(value) => fulfill(scala.util.Success(Left(value)))
}
Expand Down
2 changes: 2 additions & 0 deletions modules/core/src/main/scala/jsonrpclib/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ package object jsonrpclib {
type ErrorCode = Int
type ErrorMessage = String

private[jsonrpclib] type ~>[F[_], G[_]] = jsonrpclib.PolyFunction[F, G]

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import smithy4s.Service
import smithy4s.kinds.FunctorAlgebra
import smithy4s.kinds.FunctorInterpreter
import smithy4s.schema.Schema
import jsonrpclib.ErrorCodec
import jsonrpclib.ProtocolError
import jsonrpclib.Monadic
import jsonrpclib.Payload
import jsonrpclib.ErrorPayload
import io.circe.Codec
import jsonrpclib.Monadic.syntax._
import jsonrpclib.ErrorEncoder

object ServerEndpoints {

Expand Down Expand Up @@ -58,7 +57,7 @@ object ServerEndpoints {
impl(op)
}
case Some(errorSchema) =>
implicit val errorCodec: ErrorCodec[E] = errorCodecFromSchema(errorSchema.schema)
implicit val errorCodec: ErrorEncoder[E] = errorCodecFromSchema(errorSchema.schema)
Endpoint[F](methodName).apply[I, E, O] { (input: I) =>
val op = smithy4sEndpoint.wrap(input)
impl(op).attempt.flatMap {
Expand All @@ -70,14 +69,7 @@ object ServerEndpoints {
}
}

private def errorCodecFromSchema[A](s: Schema[A]): ErrorCodec[A] = {
new ErrorCodec[A] {

def encode(a: A): ErrorPayload = {
ErrorPayload(-32000, "JSONRPC application error", Some(Payload(CirceJson.fromSchema(s).apply(a))))

}
def decode(error: ErrorPayload): Either[ProtocolError, A] = ???
}
private def errorCodecFromSchema[A](s: Schema[A]): ErrorEncoder[A] = { (a: A) =>
ErrorPayload(-32000, "JSONRPC-smithy4s application error", Some(Payload(CirceJson.fromSchema(s).apply(a))))
}
}
Loading