- Notifications
You must be signed in to change notification settings - Fork 6
Docs and examples #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
7 commits Select commit Hold shift + click to select a range
ca3b26c
Adds installation instructions and examples
Baccata c24e8fc
It works ... but client program doesn't terminate
Baccata 221798c
Update README.md
Baccata f4ec399
Remove deps
Baccata 0a8291b
Merge branch 'docs-and-examples' of github.com:neandertech/jsonrpclib…
Baccata 6d18ee3
Change UX to be more compositional
Baccata 8e9a54d
fmt
Baccata File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,17 @@ | ||
version = "3.5.3" | ||
runner.dialect = scala213 | ||
maxColumn = 120 | ||
fileOverride { | ||
"glob:**/fs2/src/**" { | ||
runner.dialect = scala213source3 | ||
} | ||
"glob:**/fs2/test/src/**" { | ||
runner.dialect = scala213source3 | ||
} | ||
"glob:**/core/test/src-jvm-native/**" { | ||
runner.dialect = scala213source3 | ||
} | ||
"glob:**/core/src/**" { | ||
runner.dialect = scala213source3 | ||
} | ||
} |
This file contains hidden or 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,23 +1,41 @@ | ||
[](https://github.com/neandertech/jsonrpclib/actions/workflows/ci.yml) | ||
| ||
[ | ||
| ||
[ | ||
| ||
| ||
# jsonrpclib | ||
| ||
This is a cross-platform, cross-scala-version [jsonrpc](https://www.jsonrpc.org/) library that provides construct for bidirectional communication using | ||
the jsonrpc protocol. | ||
This is a cross-platform, cross-scala-version library that provides construct for bidirectional communication using the [jsonrpc](https://www.jsonrpc.org/) protocol. It is built on top of [fs2](https://fs2.io/#/) and [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) | ||
| ||
This library does not enforce any transport, and can work on top of stdin/stdout or other channels. | ||
| ||
## Installation | ||
| ||
The dependencies below are following [cross-platform semantics](http://youforgotapercentagesignoracolon.com/). | ||
Adapt according to your needs | ||
| ||
### SBT | ||
| ||
This library does not enforce any transport, and works as long as you can provide input/output byte streams. | ||
```scala | ||
libraryDependencies += "tech.neander" %%% "jsonrpclib-fs2" % version | ||
``` | ||
| ||
### Mill | ||
| ||
## Dev Notes | ||
```scala | ||
override def ivyDeps = super.ivyDeps() ++ Agg(ivy"tech.neander::jsonrpclib-fs2::$version") | ||
``` | ||
| ||
### Scala-native | ||
### Scala-cli | ||
| ||
See | ||
* https://github.com/scala-native/scala-native/blob/63d07093f6d0a6e9de28cd8f9fb6bc1d6596c6ec/test-interface/src/main/scala/scala/scalanative/testinterface/NativeRPC.scala | ||
```scala | ||
//> using lib "tech.neander::jsonrpclib-fs2:<VERSION>" | ||
``` | ||
| ||
## Usage | ||
| ||
### Scala-js | ||
**/!\ Please be aware that this library is in its early days and offers strictly no guarantee with regards to backward compatibility** | ||
| ||
See | ||
* https://github.com/scala-js/scala-js-js-envs/blob/main/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/ComSupport.scala#L245 | ||
* https://github.com/scala-js/scala-js/blob/0708917912938714d52be1426364f78a3d1fd269/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala | ||
See the examples folder |
This file contains hidden or 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 hidden or 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,15 @@ | ||
# Dev Notes | ||
| ||
In case somebody wants to implement future-based channels, here are some source of inspiration : | ||
| ||
### Scala-native | ||
| ||
See | ||
* https://github.com/scala-native/scala-native/blob/63d07093f6d0a6e9de28cd8f9fb6bc1d6596c6ec/test-interface/src/main/scala/scala/scalanative/testinterface/NativeRPC.scala | ||
| ||
| ||
### Scala-js | ||
| ||
See | ||
* https://github.com/scala-js/scala-js-js-envs/blob/main/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/ComSupport.scala#L245 | ||
* https://github.com/scala-js/scala-js/blob/0708917912938714d52be1426364f78a3d1fd269/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala |
This file contains hidden or 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,106 @@ | ||
package examples.server | ||
| ||
import jsonrpclib.CallId | ||
import jsonrpclib.fs2._ | ||
import cats.effect._ | ||
import fs2.io._ | ||
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec | ||
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker | ||
import jsonrpclib.Endpoint | ||
import cats.syntax.all._ | ||
import fs2.Stream | ||
import jsonrpclib.StubTemplate | ||
import cats.effect.std.Dispatcher | ||
import scala.sys.process.ProcessIO | ||
import cats.effect.implicits._ | ||
import scala.sys.process.{Process => SProcess} | ||
import java.io.OutputStream | ||
import java.io.InputStream | ||
| ||
object ClientMain extends IOApp.Simple { | ||
| ||
// Reserving a method for cancelation. | ||
val cancelEndpoint = CancelTemplate.make[CallId]("$/cancel", identity, identity) | ||
| ||
// Creating a datatype that'll serve as a request (and response) of an endpoint | ||
case class IntWrapper(value: Int) | ||
object IntWrapper { | ||
implicit val jcodec: JsonValueCodec[IntWrapper] = JsonCodecMaker.make | ||
} | ||
| ||
type IOStream[A] = fs2.Stream[IO, A] | ||
def log(str: String): IOStream[Unit] = Stream.eval(IO.consoleForIO.errorln(str)) | ||
| ||
def run: IO[Unit] = { | ||
import scala.concurrent.duration._ | ||
// Using errorln as stdout is used by the RPC channel | ||
val run = for { | ||
_ <- log("Starting client") | ||
serverJar <- sys.env.get("SERVER_JAR").liftTo[IOStream](new Exception("SERVER_JAR env var does not exist")) | ||
// Starting the server | ||
(serverStdin, serverStdout, serverStderr) <- Stream.resource(process("java", "-jar", serverJar)) | ||
pipeErrors = serverStderr.through(fs2.io.stderr) | ||
// Creating a channel that will be used to communicate to the server | ||
fs2Channel <- FS2Channel | ||
.lspCompliant[IO](serverStdout, serverStdin, cancelTemplate = cancelEndpoint.some) | ||
.concurrently(pipeErrors) | ||
// Opening the stream to be able to send and receive data | ||
_ <- fs2Channel.openStream | ||
// Creating a `IntWrapper => IO[IntWrapper]` stub that can call the server | ||
increment = fs2Channel.simpleStub[IntWrapper, IntWrapper]("increment") | ||
result <- Stream.eval(increment(IntWrapper(0))) | ||
_ <- log(s"Client received $result") | ||
_ <- log("Terminating client") | ||
} yield () | ||
run.compile.drain.timeout(2.second) | ||
} | ||
| ||
/** Wraps the spawning of a subprocess into fs2 friendly semantics | ||
*/ | ||
import scala.concurrent.duration._ | ||
def process(command: String*) = for { | ||
dispatcher <- Dispatcher[IO] | ||
stdinPromise <- IO.deferred[fs2.Pipe[IO, Byte, Unit]].toResource | ||
stdoutPromise <- IO.deferred[fs2.Stream[IO, Byte]].toResource | ||
stderrPromise <- IO.deferred[fs2.Stream[IO, Byte]].toResource | ||
makeProcessBuilder = IO(sys.process.stringSeqToProcess(command)) | ||
makeProcessIO = IO( | ||
new ProcessIO( | ||
in = { (outputStream: OutputStream) => | ||
val pipe = writeOutputStreamFlushingChunks(IO(outputStream)) | ||
val fulfil = stdinPromise.complete(pipe) | ||
dispatcher.unsafeRunSync(fulfil) | ||
}, | ||
out = { (inputStream: InputStream) => | ||
val stream = fs2.io.readInputStream(IO(inputStream), 512) | ||
val fulfil = stdoutPromise.complete(stream) | ||
dispatcher.unsafeRunSync(fulfil) | ||
}, | ||
err = { (inputStream: InputStream) => | ||
val stream = fs2.io.readInputStream(IO(inputStream), 512) | ||
val fulfil = stderrPromise.complete(stream) | ||
dispatcher.unsafeRunSync(fulfil) | ||
} | ||
) | ||
) | ||
makeProcess = (makeProcessBuilder, makeProcessIO).flatMapN { case (b, io) => IO.blocking(b.run(io)) } | ||
_ <- Resource.make(makeProcess)((runningProcess) => IO.blocking(runningProcess.destroy())) | ||
pipes <- (stdinPromise.get, stdoutPromise.get, stderrPromise.get).tupled.toResource | ||
} yield pipes | ||
| ||
/** Adds a flush after each chunk | ||
*/ | ||
def writeOutputStreamFlushingChunks[F[_]]( | ||
fos: F[OutputStream], | ||
closeAfterUse: Boolean = true | ||
)(implicit F: Sync[F]): fs2.Pipe[F, Byte, Nothing] = | ||
s => { | ||
def useOs(os: OutputStream): Stream[F, Nothing] = | ||
s.chunks.foreach(c => F.interruptible(os.write(c.toArray)) >> F.blocking(os.flush())) | ||
| ||
val os = | ||
if (closeAfterUse) Stream.bracket(fos)(os => F.blocking(os.close())) | ||
else Stream.eval(fos) | ||
os.flatMap(os => useOs(os) ++ Stream.exec(F.blocking(os.flush()))) | ||
} | ||
} |
This file contains hidden or 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,41 @@ | ||
package examples.server | ||
| ||
import jsonrpclib.CallId | ||
import jsonrpclib.fs2._ | ||
import cats.effect._ | ||
import fs2.io._ | ||
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec | ||
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker | ||
import jsonrpclib.Endpoint | ||
import cats.syntax.all._ | ||
| ||
object ServerMain extends IOApp.Simple { | ||
| ||
// Reserving a method for cancelation. | ||
val cancelEndpoint = CancelTemplate.make[CallId]("$/cancel", identity, identity) | ||
| ||
// Creating a datatype that'll serve as a request (and response) of an endpoint | ||
case class IntWrapper(value: Int) | ||
object IntWrapper { | ||
implicit val jcodec: JsonValueCodec[IntWrapper] = JsonCodecMaker.make | ||
} | ||
| ||
// Implementing an incrementation endpoint | ||
val increment = Endpoint[IO]("increment").simple { in: IntWrapper => | ||
IO.consoleForIO.errorln(s"Server received $in") >> | ||
IO.pure(in.copy(value = in.value + 1)) | ||
} | ||
| ||
def run: IO[Unit] = { | ||
// Using errorln as stdout is used by the RPC channel | ||
IO.consoleForIO.errorln("Starting server") >> | ||
FS2Channel | ||
.lspCompliant[IO](fs2.io.stdin[IO](bufSize = 512), fs2.io.stdout[IO], cancelTemplate = cancelEndpoint.some) | ||
.flatMap(_.withEndpointStream(increment)) // mounting an endpoint onto the channel | ||
.flatMap(_.openStreamForever) // starts the communication | ||
.compile | ||
.drain | ||
.guarantee(IO.consoleForIO.errorln("Terminating server")) | ||
} | ||
| ||
} |
This file contains hidden or 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 hidden or 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
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.