Skip to main content

Module jsonrpc

Module jsonrpc 

Source
Expand description

JSON-RPC 2.0 message types. Lifted out of lattice-lsp (IDE-protocol Risk 3) so a second peer-protocol crate (lattice-claude-code) can reuse the wire shape without an ide -> lsp crate edge. The types are transport-agnostic; each peer’s codec writes the bytes. JSON-RPC 2.0 message types – the wire shape of every LSP exchange. Transport-agnostic: these types serialize through serde_json::to_string, the codec writes the bytes.

Also the wire shape of the Claude Code IDE peer (lattice-claude-code) and the MCP server in lattice-ai; none of them owns the types, so none of them depends on another to share them. Framing (LSP’s Content-Length headers, a WebSocket frame) is each peer’s codec’s business.

§Examples

use lattice_protocol::{Message, Request, RequestId, Response, ResponseError};
use serde_json::json;

// Outgoing: build, then serialize for the codec.
let req = Request::new(RequestId::from_u64(1), "initialize", Some(json!({})));
let bytes = Message::Request(req).to_json().unwrap();
assert!(bytes.starts_with(br#"{"jsonrpc":"2.0","id":1,"method":"initialize""#));

// Incoming: decode by shape — `id` + `result` is a response.
let wire = br#"{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}"#;
let Message::Response(resp) = Message::from_json(wire).unwrap() else {
    panic!("expected a response");
};
assert_eq!(resp.id, RequestId::Number(1));
assert!(resp.error.is_none());

// `method` without `id` is a notification.
let note = br#"{"jsonrpc":"2.0","method":"$/progress","params":{}}"#;
assert!(matches!(Message::from_json(note), Ok(Message::Notification(_))));

// Answering a server-initiated request we cannot serve.
let reply = Response::err(
    RequestId::String("cfg-1".into()),
    ResponseError {
        code: lattice_protocol::jsonrpc::error_codes::METHOD_NOT_FOUND,
        message: "unsupported".into(),
        data: None,
    },
);
let json = String::from_utf8(Message::Response(reply).to_json().unwrap()).unwrap();
assert!(json.contains(r#""id":"cfg-1""#) && !json.contains("result"));

§Why we keep params / result as serde_json::Value

lsp-types has typed structs for every method. We could parameterize on <P, R> but that would push the typing into every actor and complicate dynamic dispatch (one mailbox handling 30+ method shapes). Instead the codec yields serde_json::Value-bearing messages and the actor downcasts per method via serde_json::from_value::<T>(...). That confines the type discipline to the message-handling layer where it belongs.

§Id correlation

Every outgoing request gets a fresh RequestId::Number from the actor’s monotonic counter. Incoming responses are matched against an id → oneshot map; matching is a HashMap lookup. Server-initiated requests (e.g. workspace/configuration) use their own id; we echo it back on the response unchanged.

Modules§

error_codes
Standard JSON-RPC 2.0 error codes plus the LSP-defined ones. Used by the actor to build error responses to server-initiated requests we can’t fulfil.

Structs§

Notification
One JSON-RPC notification: fire-and-forget, no response. LSP uses these for textDocument/didChange, textDocument/publishDiagnostics, $/progress, and the like.
Request
One JSON-RPC request that expects a response. params is optional per JSON-RPC; LSP methods that take no parameters should send params: null (or omit). We always include it as null to keep the wire layout uniform.
Response
One JSON-RPC response. Either result or error is set; never both.
ResponseError
JSON-RPC error envelope. code is one of the standard integers from the JSON-RPC spec (error_codes), a JSON-RPC implementation-defined server error (-32099 ..= -32000), or one of LSP’s own codes (-32899 ..= -32800); message is human-readable; data is whatever the server attaches.

Enums§

Message
One incoming or outgoing JSON-RPC message. The codec yields Message from the wire; the actor matches on the variant.
MessageDecodeError
Decode failures from Message::from_json.
RequestId
Per JSON-RPC, an id is a string, a number, or null. LSP servers in the wild send all three; clients (us) only emit Number, but we accept all on the read path.

Constants§

JSONRPC_VERSION
JSON-RPC 2.0 protocol version literal. Always exactly this string on the wire; we preserve it as-is so a non-conforming server can be flagged early.