7.3. JSON-RPC 2.0 envelope + parser, transport-agnostic

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 JSONRPC-01 — Building Requests, Parsing Responses for a hands-on tutorial.

All functions and symbols are in the jsonrpc module, use require to get access to it:

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):

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):

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:

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 daslang-live — Live-Reload Application Host (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/.

7.3.1. Constants

PARSE_ERROR = -32700

PARSE_ERROR:int const

INVALID_REQUEST = -32600

INVALID_REQUEST:int const

METHOD_NOT_FOUND = -32601

METHOD_NOT_FOUND:int const

INVALID_PARAMS = -32602

INVALID_PARAMS:int const

INTERNAL_ERROR = -32603

INTERNAL_ERROR:int const

7.3.2. Structures

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 : 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.

ParsedBatch
Fields:
  • is_batch : bool - < True when the wire body was a JSON array.

  • requests : array< 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.

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.

ParsedResponseBatch
Fields:
  • is_batch : bool - < True when the wire body was a JSON array.

  • responses : array< ParsedResponse> - < One per array element (or one for a single response).

  • parse_error : string - < Top-level parse error; mutually exclusive with responses.

7.3.3. Envelope builders

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

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

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

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:

7.3.4. Server-side parsers

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

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

7.3.5. Outgoing request builders

make_batch(messages: array<string> ): 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<string>

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

7.3.5.1. make_request

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

make_request(method: string; params_json: string; id: int ): string

7.3.6. Response parsers

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

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

7.3.7. Framing helper

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

7.3.8. High-level dispatch

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>