Overview
Quoting from the spec, JSONRPC "is transport agnostic in that the concepts can be used within the same process, over sockets, over http, or in many various message passing environments."
To model this agnosticism, the jsonrpc library uses objects of a jsonrpc-connection class, which represent a connection to a remote JSON endpoint (for details on Emacs’s object system, see EIEIO in EIEIO). In modern object-oriented parlance, this class is “abstract”, i.e. the actual class of a useful connection object is always a subclass of jsonrpc-connection. Nevertheless, we can define two distinct APIs around the jsonrpc-connection class:
An API for building JSONRPC applications
In this scenario, a new aspiring JSONRPC-based application selects a concrete subclass of
jsonrpc-connectionthat provides the transport for the JSONRPC messages to be exchanged between endpoints.The application creates objects of that subclass using
make-instance. To initiate a contact to a remote endpoint, the application passes this object to the functions such asjsonrpc-notify,jsonrpc-request, orjsonrpc-async-request.For handling remotely initiated contacts, which generally come in asynchronously, the
make-instanceinstantiation should initialize it the:request-dispatcherand:notification-dispatcherEIEIO keyword arguments. These are both functions of 3 arguments: the connection object; a symbol naming the JSONRPC method invoked remotely; and a JSONRPCparamsobject.The function passed as
:request-dispatcheris responsible for handling the remote endpoint’s requests, which expect a reply from the local endpoint (in this case, the application you’re building). Inside that function, you may either return locally (a regular return) or non-locally (throw an error). Both exits from the request dispatcher cause a reply to the remote endpoint’s request to be sent through the transport.A regular return determines a success response, and the return value must be a Lisp object that can be serialized as JSON (see Parsing and generating JSON values). The result is forwarded to the server as the JSONRPC
resultobject. A non-local return, achieved by calling the functionjsonrpc-error, causes an error response to be sent to the server. The details of the accompanying JSONRPCerrorobject are filled out with whatever was passed tojsonrpc-error. A non-local return triggered by an unexpected error of any other type also causes an error response to be sent (unless you have setdebug-on-error, in which case this calls the Lisp debugger, see Entering the Debugger on an Error).It’s possible to use the
jsonrpclibrary to build applications based on transport protocols that can be described as “quasi-JSONRPC”. These are similar, but not quite identical to JSONRPC, such as the DAP (Debug Adapter Protocol). These protocols also define request, response and notification messages but the format is not quite the same as JSONRPC. The generic functionsjsonrpc-convert-to-endpointandjsonrpc-convert-from-endpointcan be customized for converting between the internal representation of JSONRPC and whatever the endpoint accepts (see Generic Functions).An API for building JSONRPC transports
In this scenario,
jsonrpc-connectionis sub-classed to implement a different underlying transport strategy (for details on how to subclass, see Inheritance.). Users of the application-building interface can then instantiate objects of this concrete class (using themake-instancefunction) and connect to JSONRPC endpoints using that strategy. See Process-based JSONRPC connections for a built-in transport implementation.This API has mandatory and optional parts.
To allow its users to initiate JSONRPC contacts (notifications or requests) or reply to endpoint requests, the new transport implementation must equip the
jsonrpc-connection-sendgeneric function with a specialization for the new subclass (see Generic Functions). This generic function is called automatically by primitives such asjsonrpc-requestandjsonrpc-notify. The specialization should ensure that the message described in the argument list is sent through whatever underlying communication mechanism (a.k.a. “wire”) is used by the new transport to talk to endpoints. This “wire” may be a network socket, a serial interface, an HTTP connection, etc.Likewise, for handling the three types of remote contacts (requests, notifications, and responses to local requests), the transport implementation must arrange for the function
jsonrpc-connection-receiveto be called from Elisp after noticing some data on the “wire” that can be used to craft a JSONRPC (or quasi-JSONRPC) message.Finally, and optionally, the
jsonrpc-connectionsubclass should add specializations to thejsonrpc-shutdownandjsonrpc-running-pgeneric functions if these concepts apply to the transport. The specialization ofjsonrpc-shutdownshould ensure the release of any system resources (e.g. processes, timers, etc.) used to listen for messages on the wire. The specialization ofjsonrpc-running-pshould tell if these resources are still active or have already been released (viajsonrpc-shutdownor otherwise).