Skip to content

Commit c070f65

Browse files
Errors smithy4s (#86)
1 parent 830a6b0 commit c070f65

File tree

15 files changed

+438
-49
lines changed

15 files changed

+438
-49
lines changed

build.sbt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ inThisBuild(
1616
)
1717

1818
val scala213 = "2.13.16"
19-
val scala3 = "3.3.5"
19+
val scala3 = "3.3.6"
2020
val jdkVersion = 11
2121
val allScalaVersions = List(scala213, scala3)
2222
val jvmScalaVersions = allScalaVersions
@@ -40,7 +40,7 @@ val commonSettings = Seq(
4040
case Some((2, _)) => Seq(s"-target:jvm-$jdkVersion")
4141
case _ => Seq(s"-java-output-version:$jdkVersion")
4242
}
43-
}
43+
},
4444
)
4545

4646
val commonJvmSettings = Seq(
@@ -136,6 +136,23 @@ val smithy4s = projectMatrix
136136
buildTimeProtocolDependency
137137
)
138138

139+
val smithy4sTests = projectMatrix
140+
.in(file("modules") / "smithy4sTests")
141+
.jvmPlatform(jvmScalaVersions, commonJvmSettings)
142+
.jsPlatform(jsScalaVersions)
143+
.nativePlatform(Seq(scala3))
144+
.disablePlugins(AssemblyPlugin)
145+
.enablePlugins(Smithy4sCodegenPlugin)
146+
.dependsOn(smithy4s, fs2 % Test)
147+
.settings(
148+
commonSettings,
149+
publish / skip := true,
150+
libraryDependencies ++= Seq(
151+
"io.circe" %%% "circe-generic" % "0.14.7"
152+
),
153+
buildTimeProtocolDependency
154+
)
155+
139156
val exampleServer = projectMatrix
140157
.in(file("modules") / "examples/server")
141158
.jvmPlatform(List(scala213), commonJvmSettings)
@@ -235,6 +252,7 @@ val root = project
235252
exampleClient,
236253
smithy,
237254
smithy4s,
255+
smithy4sTests,
238256
exampleSmithyShared,
239257
exampleSmithyServer,
240258
exampleSmithyClient

modules/core/src/main/scala/jsonrpclib/Channel.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package jsonrpclib
22

3-
import io.circe.Codec
3+
import jsonrpclib.ErrorCodec.errorPayloadCodec
4+
import io.circe.Encoder
5+
import io.circe.Decoder
46

57
trait Channel[F[_]] {
68
def mountEndpoint(endpoint: Endpoint[F]): F[Unit]
79
def unmountEndpoint(method: String): F[Unit]
810

9-
def notificationStub[In: Codec](method: String): In => F[Unit]
10-
def simpleStub[In: Codec, Out: Codec](method: String): In => F[Out]
11-
def stub[In: Codec, Err: ErrorCodec, Out: Codec](method: String): In => F[Either[Err, Out]]
11+
def notificationStub[In: Encoder](method: String): In => F[Unit]
12+
def simpleStub[In: Encoder, Out: Decoder](method: String): In => F[Out]
13+
def stub[In: Encoder, Err: ErrorDecoder, Out: Decoder](method: String): In => F[Either[Err, Out]]
1214
def stub[In, Err, Out](template: StubTemplate[In, Err, Out]): In => F[Either[Err, Out]]
1315
}
1416

@@ -27,7 +29,7 @@ object Channel {
2729
(in: In) => F.doFlatMap(stub(in))(unit => F.doPure(Right(unit)))
2830
}
2931

30-
final def simpleStub[In: Codec, Out: Codec](method: String): In => F[Out] = {
32+
final def simpleStub[In: Encoder, Out: Decoder](method: String): In => F[Out] = {
3133
val s = stub[In, ErrorPayload, Out](method)
3234
(in: In) =>
3335
F.doFlatMap(s(in)) {
Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package jsonrpclib
22

3-
import io.circe.Codec
3+
import jsonrpclib.ErrorCodec.errorPayloadCodec
4+
import io.circe.Decoder
5+
import io.circe.Encoder
46

57
sealed trait Endpoint[F[_]] {
68
def method: String
9+
10+
def mapK[G[_]](f: PolyFunction[F, G]): Endpoint[G]
711
}
812

913
object Endpoint {
@@ -16,44 +20,51 @@ object Endpoint {
1620
class PartiallyAppliedEndpoint[F[_]](method: MethodPattern) {
1721
def apply[In, Err, Out](
1822
run: In => F[Either[Err, Out]]
19-
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): Endpoint[F] =
20-
RequestResponseEndpoint(method, (_: InputMessage, in: In) => run(in), inCodec, errCodec, outCodec)
23+
)(implicit inCodec: Decoder[In], errEncoder: ErrorEncoder[Err], outCodec: Encoder[Out]): Endpoint[F] =
24+
RequestResponseEndpoint(method, (_: InputMessage, in: In) => run(in), inCodec, errEncoder, outCodec)
2125

2226
def full[In, Err, Out](
2327
run: (InputMessage, In) => F[Either[Err, Out]]
24-
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): Endpoint[F] =
25-
RequestResponseEndpoint(method, run, inCodec, errCodec, outCodec)
28+
)(implicit inCodec: Decoder[In], errEncoder: ErrorEncoder[Err], outCodec: Encoder[Out]): Endpoint[F] =
29+
RequestResponseEndpoint(method, run, inCodec, errEncoder, outCodec)
2630

2731
def simple[In, Out](
2832
run: In => F[Out]
29-
)(implicit F: Monadic[F], inCodec: Codec[In], outCodec: Codec[Out]) =
33+
)(implicit F: Monadic[F], inCodec: Decoder[In], outCodec: Encoder[Out]) =
3034
apply[In, ErrorPayload, Out](in =>
3135
F.doFlatMap(F.doAttempt(run(in))) {
3236
case Left(error) => F.doPure(Left(ErrorPayload(0, error.getMessage(), None)))
3337
case Right(value) => F.doPure(Right(value))
3438
}
3539
)
3640

37-
def notification[In](run: In => F[Unit])(implicit inCodec: Codec[In]): Endpoint[F] =
41+
def notification[In](run: In => F[Unit])(implicit inCodec: Decoder[In]): Endpoint[F] =
3842
NotificationEndpoint(method, (_: InputMessage, in: In) => run(in), inCodec)
3943

40-
def notificationFull[In](run: (InputMessage, In) => F[Unit])(implicit inCodec: Codec[In]): Endpoint[F] =
44+
def notificationFull[In](run: (InputMessage, In) => F[Unit])(implicit inCodec: Decoder[In]): Endpoint[F] =
4145
NotificationEndpoint(method, run, inCodec)
4246

4347
}
4448

45-
final case class NotificationEndpoint[F[_], In](
49+
private[jsonrpclib] final case class NotificationEndpoint[F[_], In](
4650
method: MethodPattern,
4751
run: (InputMessage, In) => F[Unit],
48-
inCodec: Codec[In]
49-
) extends Endpoint[F]
52+
inCodec: Decoder[In]
53+
) extends Endpoint[F] {
54+
55+
def mapK[G[_]](f: PolyFunction[F, G]): Endpoint[G] =
56+
NotificationEndpoint[G, In](method, (msg, in) => f(run(msg, in)), inCodec)
57+
}
5058

51-
final case class RequestResponseEndpoint[F[_], In, Err, Out](
59+
private[jsonrpclib] final case class RequestResponseEndpoint[F[_], In, Err, Out](
5260
method: Method,
5361
run: (InputMessage, In) => F[Either[Err, Out]],
54-
inCodec: Codec[In],
55-
errCodec: ErrorCodec[Err],
56-
outCodec: Codec[Out]
57-
) extends Endpoint[F]
62+
inCodec: Decoder[In],
63+
errEncoder: ErrorEncoder[Err],
64+
outCodec: Encoder[Out]
65+
) extends Endpoint[F] {
5866

67+
def mapK[G[_]](f: PolyFunction[F, G]): Endpoint[G] =
68+
RequestResponseEndpoint[G, In, Err, Out](method, (msg, in) => f(run(msg, in)), inCodec, errEncoder, outCodec)
69+
}
5970
}

modules/core/src/main/scala/jsonrpclib/ErrorCodec.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package jsonrpclib
22

3-
trait ErrorCodec[E] {
4-
3+
trait ErrorEncoder[E] {
54
def encode(a: E): ErrorPayload
6-
def decode(error: ErrorPayload): Either[ProtocolError, E]
5+
}
76

7+
trait ErrorDecoder[E] {
8+
def decode(error: ErrorPayload): Either[ProtocolError, E]
89
}
910

11+
trait ErrorCodec[E] extends ErrorDecoder[E] with ErrorEncoder[E]
12+
1013
object ErrorCodec {
1114
implicit val errorPayloadCodec: ErrorCodec[ErrorPayload] = new ErrorCodec[ErrorPayload] {
1215
def encode(a: ErrorPayload): ErrorPayload = a

modules/core/src/main/scala/jsonrpclib/Monadic.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ object Monadic {
2626

2727
override def doMap[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
2828
}
29+
30+
object syntax {
31+
implicit class MonadicOps[F[_], A](fa: F[A]) {
32+
def flatMap[B](f: A => F[B])(implicit m: Monadic[F]): F[B] = m.doFlatMap(fa)(f)
33+
def map[B](f: A => B)(implicit m: Monadic[F]): F[B] = m.doMap(fa)(f)
34+
def attempt[B](implicit m: Monadic[F]): F[Either[Throwable, A]] = m.doAttempt(fa)
35+
def void(implicit m: Monadic[F]): F[Unit] = m.doVoid(fa)
36+
}
37+
implicit class MonadicOpsPure[A](a: A) {
38+
def pure[F[_]](implicit m: Monadic[F]): F[A] = m.doPure(a)
39+
}
40+
implicit class MonadicOpsThrowable(t: Throwable) {
41+
def raiseError[F[_], A](implicit m: Monadic[F]): F[A] = m.doRaiseError(t)
42+
}
43+
}
2944
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package jsonrpclib
2+
3+
trait PolyFunction[F[_], G[_]] { self =>
4+
def apply[A0](fa: => F[A0]): G[A0]
5+
}

modules/core/src/main/scala/jsonrpclib/internals/FutureBaseChannel.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import scala.concurrent.ExecutionContext
77
import scala.concurrent.Future
88
import scala.concurrent.Promise
99
import scala.util.Try
10-
import io.circe.Codec
1110
import io.circe.Encoder
1211

1312
abstract class FutureBasedChannel(endpoints: List[Endpoint[Future]])(implicit ec: ExecutionContext)

modules/core/src/main/scala/jsonrpclib/internals/MessageDispatcher.scala

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import jsonrpclib.Endpoint.RequestResponseEndpoint
66
import jsonrpclib.OutputMessage.ErrorMessage
77
import jsonrpclib.OutputMessage.ResponseMessage
88
import scala.util.Try
9-
import io.circe.Codec
109
import io.circe.HCursor
10+
import io.circe.Encoder
11+
import io.circe.Decoder
1112

1213
private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F]) extends Channel.MonadicChannel[F] {
1314

@@ -22,21 +23,21 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F
2223
protected def storePendingCall(callId: CallId, handle: OutputMessage => F[Unit]): F[Unit]
2324
protected def removePendingCall(callId: CallId): F[Option[OutputMessage => F[Unit]]]
2425

25-
def notificationStub[In](method: String)(implicit inCodec: Codec[In]): In => F[Unit] = { (input: In) =>
26+
def notificationStub[In](method: String)(implicit inCodec: Encoder[In]): In => F[Unit] = { (input: In) =>
2627
val encoded = inCodec(input)
2728
val message = InputMessage.NotificationMessage(method, Some(Payload(encoded)))
2829
sendMessage(message)
2930
}
3031

3132
def stub[In, Err, Out](
3233
method: String
33-
)(implicit inCodec: Codec[In], errCodec: ErrorCodec[Err], outCodec: Codec[Out]): In => F[Either[Err, Out]] = {
34+
)(implicit inCodec: Encoder[In], errDecoder: ErrorDecoder[Err], outCodec: Decoder[Out]): In => F[Either[Err, Out]] = {
3435
(input: In) =>
3536
val encoded = inCodec(input)
3637
doFlatMap(nextCallId()) { callId =>
3738
val message = InputMessage.RequestMessage(method, callId, Some(Payload(encoded)))
3839
doFlatMap(createPromise[Either[Err, Out]](callId)) { case (fulfill, future) =>
39-
val pc = createPendingCall(errCodec, outCodec, fulfill)
40+
val pc = createPendingCall(errDecoder, outCodec, fulfill)
4041
doFlatMap(storePendingCall(callId, pc))(_ => doFlatMap(sendMessage(message))(_ => future()))
4142
}
4243
}
@@ -80,13 +81,17 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F
8081
case (InputMessage.RequestMessage(_, callId, Some(params)), ep: RequestResponseEndpoint[F, in, err, out]) =>
8182
ep.inCodec(HCursor.fromJson(params.data)) match {
8283
case Right(value) =>
83-
doFlatMap(ep.run(input, value)) {
84-
case Right(data) =>
84+
doFlatMap(doAttempt(ep.run(input, value))) {
85+
case Right(Right(data)) =>
8586
val responseData = ep.outCodec(data)
8687
sendMessage(OutputMessage.ResponseMessage(callId, Payload(responseData)))
87-
case Left(error) =>
88-
val errorPayload = ep.errCodec.encode(error)
88+
case Right(Left(error)) =>
89+
val errorPayload = ep.errEncoder.encode(error)
8990
sendMessage(OutputMessage.ErrorMessage(callId, errorPayload))
91+
case Left(err) =>
92+
sendMessage(
93+
OutputMessage.ErrorMessage(callId, ErrorPayload(0, s"ServerInternalError: ${err.getMessage}", None))
94+
)
9095
}
9196
case Left(pError) =>
9297
sendProtocolError(callId, ProtocolError.ParseError(pError.getMessage))
@@ -111,13 +116,13 @@ private[jsonrpclib] abstract class MessageDispatcher[F[_]](implicit F: Monadic[F
111116
}
112117

113118
private def createPendingCall[Err, Out](
114-
errCodec: ErrorCodec[Err],
115-
outCodec: Codec[Out],
119+
errDecoder: ErrorDecoder[Err],
120+
outCodec: Decoder[Out],
116121
fulfill: Try[Either[Err, Out]] => F[Unit]
117122
): OutputMessage => F[Unit] = { (message: OutputMessage) =>
118123
message match {
119124
case ErrorMessage(_, errorPayload) =>
120-
errCodec.decode(errorPayload) match {
125+
errDecoder.decode(errorPayload) match {
121126
case Left(_) => fulfill(scala.util.Failure(errorPayload))
122127
case Right(value) => fulfill(scala.util.Success(Left(value)))
123128
}

modules/core/src/main/scala/jsonrpclib/package.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ package object jsonrpclib {
22

33
type ErrorCode = Int
44
type ErrorMessage = String
5-
65
}

modules/smithy4s/src/main/scala/jsonrpclib/smithy4sinterop/CirceJson.scala renamed to modules/smithy4s/src/main/scala/jsonrpclib/smithy4sinterop/CirceJsonCodec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import smithy4s.codecs.PayloadPath
77
import smithy4s.Document.{Decoder => _, _}
88
import io.circe._
99

10-
private[jsonrpclib] object CirceJson {
10+
object CirceJsonCodec {
1111

1212
def fromSchema[A](implicit schema: Schema[A]): Codec[A] = Codec.from(
1313
c => {

0 commit comments

Comments
 (0)