Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
413 changes: 413 additions & 0 deletions daslib/jsonrpc.das

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions doc/reflections/das2rst.das
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require daslib/rst
require daslib/functional
require daslib/json
require daslib/json_boost
require daslib/jsonrpc
require daslib/regex
require daslib/regex_boost
require daslib/apply
Expand Down Expand Up @@ -426,6 +427,19 @@ def document_module_json_boost(root : string) {
document("Boost package for JSON", mod, "json_boost.rst", groups)
}

def document_module_jsonrpc(root : string) {
var mod = find_module("jsonrpc")
var groups <- array<DocGroup>(
group_by_regex("Envelope builders", mod, %regex~(response|error|error_with_data|serialize_id)$%%),
group_by_regex("Server-side parsers", mod, %regex~(parse_request|parse_batch)$%%),
group_by_regex("Outgoing request builders", mod, %regex~(make_request|make_notification|make_batch)$%%),
group_by_regex("Response parsers", mod, %regex~(parse_response|parse_response_batch)$%%),
group_by_regex("Framing helper", mod, %regex~(compact_json_whitespace)$%%),
group_by_regex("High-level dispatch", mod, %regex~(dispatch_line)$%%)
)
document("JSON-RPC 2.0 envelope + parser, transport-agnostic", mod, "jsonrpc.rst", groups)
}

def document_module_regex(root : string) {
var mod = find_module("regex")
var groups <- array<DocGroup>(
Expand Down Expand Up @@ -1635,6 +1649,7 @@ def main {
document_module_jobque_boost(root)
document_module_json_boost(root)
document_module_json(root)
document_module_jsonrpc(root)
// document_module_linked_list(root) // spoof template, not documentable
document_module_linq(root)
document_module_linq_boost(root)
Expand Down
4 changes: 4 additions & 0 deletions doc/source/reference/language/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ Debugging and Profiling
- bool
- false
- Prints detailed compile time breakdown at the end of compilation.
* - ``log_module_compile_time``
- bool
- false
- Prints per-module compile-time breakdown (parse / infer with pass count / optimize / macro (in infer) / macro mods) plus function count for each required module. Also enables per-context simulate timing logs and the top-level aggregate summary. CLI: ``-log-compile-time``.

--------------------
RTTI
Expand Down
21 changes: 21 additions & 0 deletions doc/source/reference/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,27 @@ For the strudel-to-strudel.cc feature comparison, see
tutorials/daStrudel_15_live_reloading.rst
tutorials/daStrudel_16_hrtf_position.rst

.. _tutorials_jsonrpc:

JSON-RPC 2.0 Tutorials
=======================

These tutorials cover ``daslib/jsonrpc`` — the transport-agnostic
JSON-RPC 2.0 library: building requests, implementing servers with
``dispatch_line``, and the §6 batch semantics. The companion ``.das``
files are in ``tutorials/jsonrpc/``.

Run any tutorial from the project root::

daslang.exe tutorials/jsonrpc/01_request_response.das

.. toctree::
:maxdepth: 1

tutorials/jsonrpc_01_request_response.rst
tutorials/jsonrpc_02_dispatch_line.rst
tutorials/jsonrpc_03_batch.rst

.. _tutorials_daspeg:

dasPEG (Parser Generator) Tutorials
Expand Down
128 changes: 128 additions & 0 deletions doc/source/reference/tutorials/jsonrpc_01_request_response.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
.. _tutorial_jsonrpc_request_response:

=================================================
JSONRPC-01 — Building Requests, Parsing Responses
=================================================

.. index::
single: Tutorial; JSON-RPC
single: Tutorial; jsonrpc
single: Tutorial; daslib/jsonrpc

This tutorial covers the client side of ``daslib/jsonrpc``: building
outgoing JSON-RPC 2.0 requests, notifications, and §6 batches, then
parsing the responses you get back.

Setup
=====

Import the module:

.. code-block:: das

require daslib/jsonrpc

Building a request
==================

``make_request(method, params_json, id)`` builds a JSON-RPC 2.0 request
as a single-line string. The ``params_json`` argument is a *pre-serialized*
JSON value — pass ``"null"`` for no params, or any valid JSON literal,
object, or array. The id may be ``int`` or ``string``:

.. code-block:: das

let req1 = make_request("echo", "[\"hello\"]", 1)
// → {"jsonrpc":"2.0","id":1,"method":"echo","params":["hello"]}

let req2 = make_request("status", "null", "call-7")
// → {"jsonrpc":"2.0","id":"call-7","method":"status","params":null}

Notifications
=============

A notification omits the ``id`` field. Per JSON-RPC 2.0 §4.1, the server
MUST NOT send a response. Use these for fire-and-forget side effects:

.. code-block:: das

let notif = make_notification("log", "\{\"msg\":\"ready\"}")
// → {"jsonrpc":"2.0","method":"log","params":{"msg":"ready"}}

Building a §6 batch
====================

``make_batch(messages)`` wraps an array of pre-built request /
notification strings as a JSON array. The server returns an array of
responses, with notification entries suppressed:

.. code-block:: das

let entries <- [
make_request("status", "null", 10),
make_request("echo", "[1, 2, 3]", 11),
make_notification("log", "\{\"level\":\"info\"}")
]
let batch = make_batch(entries)

Parsing a success response
===========================

``parse_response(line)`` returns a ``ParsedResponse`` struct. Check
``is_success`` to distinguish ``{"result":...}`` from ``{"error":...}``:

.. code-block:: das

let wire = "\{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[\"hello\"]}"
let r = parse_response(wire)
// r.is_success = true
// r.id_str = "1"
// r.result_json = ["hello"]

Parsing an error response
==========================

Error responses populate ``error_code``, ``error_msg``, and optionally
``error_data``. Standard codes are constants: ``PARSE_ERROR``,
``INVALID_REQUEST``, ``METHOD_NOT_FOUND``, ``INVALID_PARAMS``,
``INTERNAL_ERROR``:

.. code-block:: das

let wire = "\{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":\{\"code\":-32601,\"message\":\"method not found\"}}"
let r = parse_response(wire)
// r.is_success = false
// r.error_code = -32601 (matches METHOD_NOT_FOUND)
// r.error_msg = "method not found"

Parsing a batched response
===========================

``parse_response_batch`` detects whether the wire was a single response
or a JSON array. For batches, ``is_batch=true`` and ``responses[]``
holds one entry per array element:

.. code-block:: das

let wire = "[\{\"id\":10,\"result\":\"ok\"},\{\"id\":11,\"error\":\{\"code\":-32602,\"message\":\"bad\"}}]"
let pb = parse_response_batch(wire)
for (entry in pb.responses) {
if (entry.is_success) print("[{entry.id_str}] result: {entry.result_json}\n")
else print("[{entry.id_str}] error {entry.error_code}: {entry.error_msg}\n")
}

Running the tutorial
====================

::

daslang.exe tutorials/jsonrpc/01_request_response.das

Full source: :download:`tutorials/jsonrpc/01_request_response.das <../../../../tutorials/jsonrpc/01_request_response.das>`

See also
========

* **Next:** :ref:`tutorial_jsonrpc_dispatch_line` — implementing a server with ``dispatch_line``
* :ref:`tutorial_jsonrpc_batch` — §6 batches end-to-end
* :ref:`stdlib_jsonrpc` — module reference
95 changes: 95 additions & 0 deletions doc/source/reference/tutorials/jsonrpc_02_dispatch_line.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
.. _tutorial_jsonrpc_dispatch_line:

=====================================================
JSONRPC-02 — Implementing a Server with dispatch_line
=====================================================

.. index::
single: Tutorial; JSON-RPC; server
single: Tutorial; jsonrpc; dispatch_line

This tutorial shows the server side of ``daslib/jsonrpc``: the
``dispatch_line`` convenience that wraps parsing, envelope building,
notification suppression, and batch fan-out in one call. The lower-level
``parse_request`` + envelope-builder path is also covered for cases
where ``dispatch_line`` doesn't fit.

The dispatcher block
====================

``dispatch_line(line, strict, dispatcher_block)`` parses a wire line and
invokes the dispatcher block for each non-notification request. The
block receives ``(method, params_json)`` and returns the raw JSON result
string; the library wraps the result in a JSON-RPC envelope.

.. code-block:: das

require daslib/jsonrpc

def dispatch(method, params_json : string) : string {
if (method == "ping") return "\"pong\""
if (method == "echo") return jsonrpc::compact_json_whitespace(params_json)
return "\{\"error\":\"unknown: {method}\"}"
}

[export]
def main() {
let resp = jsonrpc::dispatch_line("\{\"id\":1,\"method\":\"ping\"}", false) $(m, p) {
return dispatch(m, p)
}
print("{resp}\n")
// → {"jsonrpc":"2.0","id":1,"result":"pong"}
}

Notification semantics
======================

For a notification (no ``id``), the dispatcher still runs (so side
effects happen) but the return value is discarded and ``dispatch_line``
returns ``""``. Per JSON-RPC 2.0 §4.1, the wire MUST stay silent.

Top-level parse failure
=======================

If the wire line isn't valid JSON, ``dispatch_line`` returns a pre-built
``PARSE_ERROR`` envelope and the dispatcher is never called. Same for
top-level §6 violations such as the empty array ``[]``.

Strict vs permissive
====================

``dispatch_line(line, strict=false)`` is permissive: the ``jsonrpc``
field is optional and any string value is accepted. Pass ``strict=true``
to enforce §4 — a missing or non-``"2.0"`` ``jsonrpc`` field yields
``INVALID_REQUEST``.

When dispatch_line doesn't fit
==============================

``dispatch_line`` wraps all dispatcher results in a *success* envelope.
When you need to emit specific error codes per method
(``METHOD_NOT_FOUND``, ``INVALID_PARAMS``), drop down to
``parse_request`` + envelope builders directly:

.. code-block:: das

let req = parse_request(line)
if (!empty(req.error_envelope)) return req.is_notification ? "" : req.error_envelope
if (req.method == "unknown") return error(req.id_str, METHOD_NOT_FOUND, "no such method")
return response(req.id_str, dispatch(req.method, req.params_json))

Running the tutorial
====================

::

daslang.exe tutorials/jsonrpc/02_dispatch_line.das

Full source: :download:`tutorials/jsonrpc/02_dispatch_line.das <../../../../tutorials/jsonrpc/02_dispatch_line.das>`

See also
========

* :ref:`tutorial_jsonrpc_request_response` — building requests, parsing responses (the client side)
* **Next:** :ref:`tutorial_jsonrpc_batch` — §6 batches end-to-end
* :ref:`stdlib_jsonrpc` — module reference
Loading
Loading