package pidgio

  1. Overview
  2. Docs

Module PidgioSource

A library for writing JSON-RPC 2.0 servers that communicate over a process's standard streams rather than a socket. You declare a set of methods (using Highway), each with its own parameter decoding and result encoding (using Pidgin), and the library handles the rest of the protocol: reading framed messages from stdin, parsing and validating envelopes, routing requests to handlers, distinguishing requests from notifications, serialising results and errors back to stdout, and reporting malformed input with the standard error codes.

This is the transport used by the Language Server Protocol and by editor tooling generally, where the client spawns the server as a child process and speaks to it through pipes.

Every JSONRPC entry-point is parametrized by a Highway path (instead of just a method) and the handler of an entrypoint use Primavera for abstracting dependencies/effects.

This makes it possible to quickly set up servers that interact with other programs (such as Emacs).

Buidling JSON Handler

The library does not handle JSON decoding/encoding; you can provide your own implementation that adheres to the signature, or use the pidgio-yojson package, which is based on Yojson.Basic.

Sourcemodule Json = Make.Json

Building Server Handler

As with JSON, you can choose to define your own stream provider. To be generic, the module must implement a Monad (which can be the identity monad) and stream read/write primitives. You can just use pidgio-unix (that just relies on the Unix module) or pidgio-eio (based on Eio).

Sourcemodule Server = Make.Server

Big Picture

First, we're going to set up a server on Unix and Yojson:

module Server = Pidgio_unix.Make (Pidgio_yojson)

The Server module provides a comprehensive set of tools for describing the server's behavior:

  • Functions for describing routes and their parameters
  • An Eff module that provides a Primavera instance for the monad used to describe the IO layer.

Now we can describe our first service:

let ping =
  let open Server in
  straight
    ([ s "ping" ] & ignore_param)
    ~to_pidgin:Pidgin.Repr.string (* The way to encode the result. *)
    (fun [ (* Highway args *) ] () (* <- param *) ->
       (* Perform an effect for the flex *)
       let* () = Eff.perform (fun h -> h#play_ping_pong) in
       (* Write the final result... pong, that will be included
          into a proper response. *)
       return "pong")
;;

We can now start our server, specifying the list of services we want to support and the handler to interpret the events. In this case, we have only one:

let () =
  let handler =
    object
      method play_ping_pong = (* Does nothing. lol *) ()
    end
  in
  S.run ~handler [ ping ]
;;

You can refer to the tests for some slightly more challenging examples.

Internal modules

module Sigs : sig ... end

Reusable Interfaces.

Sourcemodule Util : sig ... end

Some generics helpers mostly for internal development.

Sourcemodule Error : sig ... end

Describes the errors that can be propagated by the server (and provides their Pidgin decoders).

Sourcemodule Request : sig ... end

A request is what the Pidgio router interprets to determine which service to run.

Sourcemodule Response : sig ... end

Some helpers for dealing with server's response.

Sourcemodule Route : sig ... end

Describe a route. In the JSONRPC, it is the field method.

Sourcemodule Make : sig ... end

A set of functors for building generic handlers (notably for JSON and for describing a server).