.. _stdlib_jsonrpc: ================================================== JSON-RPC 2.0 envelope + parser, transport-agnostic ================================================== .. das:module:: jsonrpc The JSON-RPC module is a transport-agnostic JSON-RPC 2.0 implementation (https://www.jsonrpc.org/specification). It provides envelope builders, request/response parsers, optional §6 batch handling, and a high-level ``dispatch_line`` convenience for stdio-style servers. See :ref:`tutorial_jsonrpc_request_response` for a hands-on tutorial. All functions and symbols are in the ``jsonrpc`` module, use require to get access to it: .. code-block:: das require daslib/jsonrpc The library is permissive by default — the ``jsonrpc`` request member may be absent or carry a value other than ``"2.0"`` (real-world clients are lazy about it). Pass ``strict=true`` to ``parse_request`` / ``parse_batch`` / ``dispatch_line`` for full §4 compliance. Standard error codes are exposed as ``let public`` constants: ============================ ========================================= Constant Code ============================ ========================================= ``PARSE_ERROR`` ``-32700`` Invalid JSON received ``INVALID_REQUEST`` ``-32600`` Not a valid request object ``METHOD_NOT_FOUND`` ``-32601`` Method does not exist ``INVALID_PARAMS`` ``-32602`` Invalid method params ``INTERNAL_ERROR`` ``-32603`` Internal JSON-RPC error ============================ ========================================= Server-side example (one-shot): .. code-block:: das require daslib/jsonrpc [export] def main() { let wire = "\{\"id\":1,\"method\":\"ping\"}" let response = jsonrpc::dispatch_line(wire, false) $(method, params_json) { if (method == "ping") return "\"pong\"" return "\"unknown\"" } print("{response}\n") // → {"jsonrpc":"2.0","id":1,"result":"pong"} } Client-side example (build + parse): .. code-block:: das require daslib/jsonrpc [export] def main() { let wire = make_request("echo", "[1,2,3]", 7) // → {"jsonrpc":"2.0","id":7,"method":"echo","params":[1,2,3]} let reply_wire = "\{\"jsonrpc\":\"2.0\",\"id\":7,\"result\":[1,2,3]}" let r = parse_response(reply_wire) if (r.is_success) print("got result for id={r.id_str}\n") } §6 batch — multiple messages in one wire payload: .. code-block:: das let entries <- [ make_request("a", "null", 1), make_notification("log", "\{\"msg\":\"hi\"}"), make_request("b", "null", 2) ] let batch = make_batch(entries) let response = jsonrpc::dispatch_line(batch, false) $(method, params_json) { return "\"ok-{method}\"" } // response is a JSON array with two entries (notification suppressed). The ``compact_json_whitespace`` helper flattens pretty-printed ``write_json`` output to a single line — useful when piping ``daslib/json`` output through a newline-framed JSON-RPC transport. In-tree consumers include :doc:`/reference/utils/daslang_live` (stdio JSON-RPC transport for live commands) and the daslang MCP server (``utils/mcp/protocol.das``). For a complete pedagogical client+server pair, see ``examples/mcp/echo/``. +++++++++ Constants +++++++++ .. _global-jsonrpc-PARSE_ERROR: .. das:attribute:: PARSE_ERROR = -32700 PARSE_ERROR:int const .. _global-jsonrpc-INVALID_REQUEST: .. das:attribute:: INVALID_REQUEST = -32600 INVALID_REQUEST:int const .. _global-jsonrpc-METHOD_NOT_FOUND: .. das:attribute:: METHOD_NOT_FOUND = -32601 METHOD_NOT_FOUND:int const .. _global-jsonrpc-INVALID_PARAMS: .. das:attribute:: INVALID_PARAMS = -32602 INVALID_PARAMS:int const .. _global-jsonrpc-INTERNAL_ERROR: .. das:attribute:: INTERNAL_ERROR = -32603 INTERNAL_ERROR:int const ++++++++++ Structures ++++++++++ .. _struct-jsonrpc-ParsedRequest: .. das:attribute:: ParsedRequest :Fields: * **id_str** : string - < Serialized id ("null" / "7" / "\"abc\"") — embed verbatim into responses. * **method** : string - < Method name. Empty when the request was malformed. * **params_json** : string - < Raw serialized ``params`` value (e.g. ``"null"``, ``"[1,2]"``, ``"{\"x\":1}"``). * **params** : :ref:`JsonValue `? - < Pre-parsed ``params`` JsonValue; null when absent. Saves callers a second ``read_json``. * **is_notification** : bool - < True when the request omits ``id`` (spec §4.1 — MUST NOT send a response). * **error_envelope** : string - < Pre-built error response for malformed input. Empty when the request was well-formed. .. _struct-jsonrpc-ParsedBatch: .. das:attribute:: ParsedBatch :Fields: * **is_batch** : bool - < True when the wire body was a JSON array. * **requests** : array< :ref:`ParsedRequest `> - < One entry per array element (or one for a single request). * **framing_error** : string - < Top-level error envelope to send as-is (parse failure, empty array). Mutually exclusive with ``requests``. .. _struct-jsonrpc-ParsedResponse: .. das:attribute:: ParsedResponse :Fields: * **id_str** : string - < Echoed id from the response. * **is_success** : bool - < True when the response carries ``result``; false when it carries ``error``. * **result_json** : string - < Raw ``result`` value; valid when ``is_success``. * **error_code** : int - < Error code; 0 when ``is_success``. * **error_msg** : string - < Error message; empty when ``is_success``. * **error_data** : string - < Optional ``error.data`` field; empty when absent. * **parse_error** : string - < Non-empty when the response wire itself was malformed. .. _struct-jsonrpc-ParsedResponseBatch: .. das:attribute:: ParsedResponseBatch :Fields: * **is_batch** : bool - < True when the wire body was a JSON array. * **responses** : array< :ref:`ParsedResponse `> - < One per array element (or one for a single response). * **parse_error** : string - < Top-level parse error; mutually exclusive with ``responses``. +++++++++++++++++ Envelope builders +++++++++++++++++ * :ref:`error (id_str: string; code: int; message: string) : string ` * :ref:`error_with_data (id_str: string; code: int; message: string; data_json: string) : string ` * :ref:`response (id_str: string; result_json: string) : string ` * :ref:`serialize_id (id_val: JsonValue?) : string ` .. _function-jsonrpc_error_string_int_string: .. das:function:: error(id_str: string; code: int; message: string) : string Wrap ``code`` + ``message`` in a JSON-RPC 2.0 error response envelope. :Arguments: * **id_str** : string * **code** : int * **message** : string .. _function-jsonrpc_error_with_data_string_int_string_string: .. das:function:: error_with_data(id_str: string; code: int; message: string; data_json: string) : string Like ``error``, but also includes the optional ``data`` field (spec §5.1). ``data_json`` is embedded verbatim — must be valid JSON. :Arguments: * **id_str** : string * **code** : int * **message** : string * **data_json** : string .. _function-jsonrpc_response_string_string: .. das:function:: response(id_str: string; result_json: string) : string Wrap ``result_json`` in a JSON-RPC 2.0 success response envelope. ``id_str`` and ``result_json`` are embedded verbatim — both must be valid JSON. :Arguments: * **id_str** : string * **result_json** : string .. _function-jsonrpc_serialize_id_JsonValue_q_: .. das:function:: serialize_id(id_val: JsonValue?) : string Serialize a JSON-RPC id (string/number/null) to its wire form. Returns ``"null"`` when ``id_val`` is null. :Arguments: * **id_val** : :ref:`JsonValue `? +++++++++++++++++++ Server-side parsers +++++++++++++++++++ * :ref:`parse_batch (line: string; strict: bool = false) : ParsedBatch ` * :ref:`parse_request (line: string; strict: bool = false) : ParsedRequest ` .. _function-jsonrpc_parse_batch_string_bool: .. das:function:: parse_batch(line: string; strict: bool = false) : ParsedBatch Parse a JSON-RPC 2.0 wire body that may be a single request or a §6 batch array. Semantics: * Single object → ``is_batch=false``, ``requests`` has one entry. * Array of objects → ``is_batch=true``, ``requests`` has one entry per element. * Empty array ``[]`` → ``is_batch=true``, ``requests`` empty, ``framing_error`` carries a pre-built INVALID_REQUEST envelope (spec §6). * Top-level parse failure → ``is_batch=false``, ``requests`` empty, ``framing_error`` carries a PARSE_ERROR envelope. :Arguments: * **line** : string * **strict** : bool .. _function-jsonrpc_parse_request_string_bool: .. das:function:: parse_request(line: string; strict: bool = false) : ParsedRequest Parse a single JSON-RPC 2.0 request line. Pure function — testable in isolation. With ``strict=false`` (default), the ``jsonrpc`` field is optional and any string value is accepted. With ``strict=true``, missing or non-``"2.0"`` ``jsonrpc`` yields INVALID_REQUEST. All other §4 rules — id type, notification semantics, required ``method``, ``params`` type — are enforced in both modes. :Arguments: * **line** : string * **strict** : bool +++++++++++++++++++++++++ Outgoing request builders +++++++++++++++++++++++++ * :ref:`make_batch (messages: array\) : string ` * :ref:`make_notification (method: string; params_json: string) : string ` * :ref:`make_request (method: string; params_json: string; id: string) : string ` * :ref:`make_request (method: string; params_json: string; id: int) : string ` .. _function-jsonrpc_make_batch_array_ls_string_gr_: .. das:function:: make_batch(messages: array) : string Wrap pre-built request/notification strings as a JSON-RPC 2.0 §6 batch array. Each element of ``messages`` must be a valid request or notification. Empty input returns ``"[]"`` — note that ``[]`` is itself a -32600 invalid request per the spec, so this is mostly useful for testing the rejection path. :Arguments: * **messages** : array .. _function-jsonrpc_make_notification_string_string: .. das:function:: make_notification(method: string; params_json: string) : string Build a JSON-RPC 2.0 notification (no id — server MUST NOT respond). :Arguments: * **method** : string * **params_json** : string make_request ^^^^^^^^^^^^ .. _function-jsonrpc_make_request_string_string_string: .. das:function:: make_request(method: string; params_json: string; id: string) : string Build a JSON-RPC 2.0 request with a string id. :Arguments: * **method** : string * **params_json** : string * **id** : string .. _function-jsonrpc_make_request_string_string_int: .. das:function:: make_request(method: string; params_json: string; id: int) : string ++++++++++++++++ Response parsers ++++++++++++++++ * :ref:`parse_response (line: string) : ParsedResponse ` * :ref:`parse_response_batch (line: string) : ParsedResponseBatch ` .. _function-jsonrpc_parse_response_string: .. das:function:: parse_response(line: string) : ParsedResponse Parse a single JSON-RPC 2.0 response line. ``is_success`` distinguishes ``{"result":...}`` from ``{"error":...}``. Optional ``error.data`` is surfaced as the raw JSON string in ``error_data`` (empty when absent). Malformed wires set ``parse_error``. :Arguments: * **line** : string .. _function-jsonrpc_parse_response_batch_string: .. das:function:: parse_response_batch(line: string) : ParsedResponseBatch Parse a JSON-RPC 2.0 response wire body that may be a single response or a §6 batch array. Semantics mirror ``parse_batch``: a top-level array sets ``is_batch=true``; a single object sets ``is_batch=false`` with one entry. Top-level parse failure populates ``parse_error``. :Arguments: * **line** : string ++++++++++++++ Framing helper ++++++++++++++ * :ref:`compact_json_whitespace (s: string) : string ` .. _function-jsonrpc_compact_json_whitespace_string: .. das:function:: compact_json_whitespace(s: string) : string Strip JSON pretty-printer whitespace (spaces, tabs, newlines, carriage returns) that appears outside string literals. Whitespace inside ``"..."`` is preserved verbatim, including backslash-escaped quotes. Used to flatten ``write_json`` output for one-message-per-line wire framing. :Arguments: * **s** : string +++++++++++++++++++ High-level dispatch +++++++++++++++++++ * :ref:`dispatch_line (line: string; strict: bool; dispatcher: block\<(method:string;params_json:string):string\>) : string ` .. _function-jsonrpc_dispatch_line_string_bool_block_ls_method_c_string;params_json_c_string_c_string_gr_: .. das:function:: dispatch_line(line: string; strict: bool; dispatcher: block<(method:string;params_json:string):string>) : string Dispatch a JSON-RPC 2.0 wire line (single or §6 batch) through ``dispatcher``, returning the response string ready for the wire (or ``""`` for all-notifications). The dispatcher block receives ``(method, params_json)`` for each non-notification request and returns a raw JSON result string. The library wraps the result in a JSON-RPC envelope; notification semantics, batch wrapping, framing-error suppression, and input-order preservation are all handled here. Caller-side error reporting: to signal ``METHOD_NOT_FOUND`` or other per-method errors from the dispatcher, return a JSON object the wrapper treats as result — or use ``parse_request`` / ``parse_batch`` directly and emit ``error(...)`` envelopes yourself when the protocol requires it. :Arguments: * **line** : string * **strict** : bool * **dispatcher** : block<(method:string;params_json:string):string>