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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
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
paramsvalue (e.g."null","[1,2]","{\"x\":1}").params : JsonValue? - < Pre-parsed
paramsJsonValue; null when absent. Saves callers a secondread_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 carrieserror.result_json : string - < Raw
resultvalue; valid whenis_success.error_code : int - < Error code; 0 when
is_success.error_msg : string - < Error message; empty when
is_success.error_data : string - < Optional
error.datafield; 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:
id_val : JsonValue?
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,requestshas one entry.Array of objects →
is_batch=true,requestshas one entry per element.Empty array
[]→is_batch=true,requestsempty,framing_errorcarries a pre-built INVALID_REQUEST envelope (spec §6).Top-level parse failure →
is_batch=false,requestsempty,framing_errorcarries 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>