moonjrpc

API

Version 0.1.3. Every public type, function and value, from the sources.

moonjrpc

Messages and the method table. No I/O.

suberrorFailed

pub(all) suberror Failed {
  Failed(Failure)
  TimedOut(name~ : String, limit~ : Int32)
  Closed(name~ : String)
  TooLong(limit~ : Int32, got~ : Int64)
  Malformed(String)
}

What goes wrong with a call. Failed is the other side's error object, and a handler raises it to answer with one. limit~ is the bound that was passed and got~ what came.

structFailure

pub(all) struct Failure {
  code : Int32
  message : String
  data : Json?
}

The error object of a response.

enumId

pub(all) enum Id {
  Num(Double, repr~ : String?)
  Text(String)
  Null
}

A request's identifier. A number keeps the text it was written in, as core's Json does, so an identifier Double cannot hold goes back out exactly as it came in.

fnId::int

pub fn Id::int(n : Int64) -> Id

An identifier from an integer.

fnId::key

pub fn Id::key(self : Id) -> String

The same text for the same identifier, whichever side wrote it: a map key for matching replies to calls.

enumMessage

pub(all) enum Message {
  Call(id~ : Id, name~ : String, params~ : Json?)
  Notice(name~ : String, params~ : Json?)
  Reply(id~ : Id, result~ : Json)
  Refusal(id~ : Id, failure~ : Failure)
}

One JSON-RPC 2.0 message. A Refusal is a response carrying an error.

fnMessage::invalid

pub fn Message::invalid(json : Json) -> Message

The answer to something that is not a valid message: -32600, with its identifier if one can be read and null otherwise.

fnMessage::unreadable

pub fn Message::unreadable() -> Message

The answer to text that is not JSON: -32700, with a null identifier.

structMethods

struct Methods

The method table: what a side does with the calls and notices it receives. Registering a name again replaces it.

fnMethods::new

pub fn Methods::new(on_error? : (String, Error) -> Unit) -> Methods

A table with nothing in it. on_error hears the errors a client is not told about: a handler's failure other than Failed, which goes out as -32603 with no detail, and any failure of a notice, which has no one to go to.

fnMethods::call

pub fn Methods::call(self : Methods, name : String, run : async (Json?) -> Json) -> Unit

Answer calls to name with what run returns. The parameters arrive as they came.

fnMethods::notice

pub fn Methods::notice(self : Methods, name : String, run : async (Json?) -> Unit) -> Unit

Run run for each notice named name. Nothing goes back.

fnMethods::typed

pub fn[P : @json.FromJson, R : ToJson] Methods::typed(self : Methods, name : String, run : async (P) -> R) -> Unit

A call whose parameters and result go through FromJson and ToJson. Parameters that do not decode are -32602, with the decoder's reason as the error's data.

fnMethods::answer

pub async fn Methods::answer(self : Methods, message : Message) -> Message?

What to send back for a message: the response to a call, nothing for the rest. A method nobody registered is -32601 and a raised Failed goes out as it is; any other error is -32603 with no detail, because what a handler trips over is not the caller's to read.

fnMethods::body

pub async fn Methods::body(self : Methods, text : StringView, batch~ : Bool = true) -> String?

One body in, one body out, by the specification: text that is not JSON is -32700, a value that is not a message is -32600, an array is a batch, and when nothing needs answering there is nothing to send. batch=false is for protocols that forbid arrays, as ACP does: an array is then -32600.

fnfail

pub fn[T] fail(code : Int32, message : String, data? : Json) -> T raise Failed

Raise an error object: fail(invalid_params, "no such file").

letinternal_error

pub let internal_error : Int32

The handler failed in a way the caller is not told about.

letinvalid_params

pub let invalid_params : Int32

The parameters are not what the method takes.

letinvalid_request

pub let invalid_request : Int32

The value is not a valid message.

letmethod_not_found

pub let method_not_found : Int32

Nobody registered that method.

letparse_error

pub let parse_error : Int32

The text is not JSON.

moonjrpc/peer

Two-way calls over any transport, and the two framings.

traitFrames

pub(open) trait Frames {
  async fn read(Self) -> String?
  async fn write(Self, String) -> Unit
}

How one message's text travels: a line, a WebSocket frame, an HTTP body. Implement this to carry JSON-RPC over anything.

structPeer

struct Peer

One end of a conversation in which either side may call the other. Messages are taken in the order they arrive: a notice finishes before the next message is looked at, a call starts in its turn and runs on in a task of its own, and a reply wakes its caller in its turn. So the updates a side sends before answering are handled before its caller sees the answer. A notice handler that waits on Peer::call waits for a reply that cannot be looked at until the handler returns. Start a task for that instead.

fnPeer::new

pub fn Peer::new(frames : &Frames, methods~ : @moonjrpc.Methods = @moonjrpc.Methods::new(), timeout_ms~ : Int32? = Some(timeout_ms), batch~ : Bool = true) -> Peer

timeout_ms=None waits as long as it takes. batch=false answers an array with -32600, for protocols that forbid batches.

fnPeer::call

pub async fn Peer::call(self : Peer, name : String, params? : Json, timeout_ms~ : Int32? = self.timeout_ms) -> Json

Send a call and wait for its answer. The other side's error object raises Failed; no answer in time raises TimedOut, and the side going away raises Closed. Telling the other side the call was given up on is for the protocol to do: MCP, ACP and LSP each have their own notice for it.

fnPeer::notify

pub async fn Peer::notify(self : Peer, name : String, params? : Json) -> Unit

Send a notice. It raises Closed once the other side has gone away.

fnPeer::run

pub async fn Peer::run(self : Peer) -> Unit

Read until the other side closes. Calls still running are waited for, so a client that writes its requests and closes its end still gets its answers; cancel this task to stop sooner.

fncontent_length

pub fn content_length(reader : &@io.Reader, writer : &@io.Writer, limit~ : Int32 = limit) -> &Frames

A Content-Length header, a blank line, the body: the base protocol of LSP. A message over limit bytes raises TooLong.

letlimit

pub let limit : Int32

The longest message lines and content_length take: 64 MiB. vscode-jsonrpc and the MCP SDKs set no bound, gRPC's default is 4 MiB; no common value exists, so this takes the safe side without refusing what a large tool result needs.

fnlines

pub fn lines(reader : &@io.Reader, writer : &@io.Writer, limit~ : Int32 = limit) -> &Frames

One message to a line: the stdio transport of MCP and ACP. Blank lines are skipped, a last line with no newline still counts, and a line over limit bytes raises TooLong.

lettimeout_ms

pub let timeout_ms : Int32

How long Peer::call waits: 60 seconds, the MCP TypeScript SDK's DEFAULT_REQUEST_TIMEOUT_MSEC.