proto: give the generated C++ Dispatcher a real error channel (P1, 1.4.0)

DAEMON's daemon/docs/proto-requests-m1.md P1: velox::proto::Dispatcher's
on_* methods returned Result<T> = expected<T, ParseError>, and dispatch()
mapped every handler error to -32603 InternalError. A handler had no way to
return -32010 (download.get not-found), -32011 (download.add invalid-path)
or -32013 (probe-failed) with their data payloads -- three error fixtures a
conformant server must satisfy were unreachable, blocking DAEMON's
"conformance as a server" M1 DoD.

Two error channels now, kept separate on purpose:
  - parse: Result<T> / ParseError -- dispatch() failing to turn the wire into
    typed params. Always -32602, always structural.
  - handler: HandlerResult<T> / HandlerError -- a handler deciding the request
    can't be fulfilled. Carries any ErrorCode + message + free-form data.

    struct HandlerError {
        ErrorCode code{ErrorCode::InternalError};  // bare {} is a valid -32603
        std::string message;
        nlohmann::json data = nullptr;             // straight into the error's data
    };
    template <class T> using HandlerResult = std::expected<T, HandlerError>;

dispatch()'s handler branch is now
  make_error(id, r.error().code, r.error().message, r.error().data)
instead of a hard-coded InternalError. -32001/-32002/-32003 stay the server
layer's to raise around dispatch(), as DAEMON already does.

Verified end to end against the real dispatch() path: a handler returning
TaskNotFound/InvalidPath/ProbeFailed produces -32010/-32011/-32013 with the
data object intact, and a bare HandlerError{} still yields a clean -32603
with no data field. The `= nullptr` on the member (not `{nullptr}`) matters:
brace-init of nlohmann::json from nullptr is the array [null], not JSON null.

FixtureDispatcher regenerated to HandlerResult; conformance_main.cpp only
inspects dispatch()'s JSON and needed no change. TS side is untouched beyond
the version string -- no server Dispatcher is generated there.

P2 also handled: session.hello.version-mismatch's data.expected was a stale
"1.0.0"; now $any, with a note that the error-fixture compare is on `code`
only so a server echoing kProtocolVersion there is fine.

Version: minor, 1.3.0 -> 1.4.0. Wire is byte-identical (no schema, fixture,
or OpenRPC change) but every Dispatcher implementer must swap Result ->
HandlerResult on regen, and the bump is how lanes are told to. Not an ADR:
one lane consumes this binding, it's the one that asked, and the shape is
the one they proposed. Answered in contracts/proto-answers-daemon-m1.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
This commit is contained in:
2026-09-10 15:10:13 +04:00
co-authored by Claude Sonnet 5
parent 9dce588456
commit 5e3e21543a
15 changed files with 298 additions and 190 deletions
+10 -8
View File
@@ -3,16 +3,18 @@
**This directory is the interface between every lane.** Owner: agent **PROTO**. **This directory is the interface between every lane.** Owner: agent **PROTO**.
Nobody else commits here. Everybody else *generates from* here. Nobody else commits here. Everybody else *generates from* here.
> ## Status: **v1.3.0** (frozen at v1.0.0 on 2026-09-09; minor bumps since) > ## Status: **v1.4.0** (frozen at v1.0.0 on 2026-09-09; minor bumps since)
> >
> **v1.0.0** froze 38 methods, 9 events, 26 named types. **v1.1.0** widened `bufferBytes` > **v1.0.0** froze 38 methods, 9 events, 26 named types. **v1.1.0** widened `bufferBytes`
> bounds and added the segment-budget settings (`docs/adr/0012-...`). **v1.2.0** added > bounds and added the segment-budget settings. **v1.2.0** added `download.provideAuth`
> `download.provideAuth`, F2's credential return path. **v1.3.0** (current) widens when > (F2). **v1.3.0** widened when `error` is populated on a state change to cover a
> `event.task.state.error` / `TaskSummary.error` are populated to also cover a `paused` > daemon-initiated `paused` (for `docs/adr/0013-...`). **v1.4.0** (current) is a
> the daemon entered unilaterally (`auth_required`, `server_file_changed`, disk full), > **C++-binding-only** change: the generated `Dispatcher` gains a `HandlerError` /
> not just `failed`/`retry_wait` — the wire shape is unchanged (`error` was already > `HandlerResult<T>` error channel so a handler can return `-32010` / `-32011` /
> `TaskError | null`), only the description of when it's set. Landed for DAEMON's > `-32013` with their `data` payloads instead of collapsing to `-32603`. The wire is
> `docs/adr/0013-task-state-machine-ownership.md`. See also `docs/adr/0005-...` for the > byte-identical — no schema or fixture change — but any `Dispatcher` implementer
> must swap `Result` → `HandlerResult` on regen. Answered in
> `contracts/proto-answers-daemon-m1.md`. See also `docs/adr/0005-...` for the
> versioning rule and `docs/adr/0010-...` for the failure taxonomy and segment ranges. > versioning rule and `docs/adr/0010-...` for the failure taxonomy and segment ranges.
> >
> Lane requests are answered in writing: `contracts/proto-answers-m1.md` responds to > Lane requests are answered in writing: `contracts/proto-answers-m1.md` responds to
+1 -1
View File
@@ -1 +1 @@
1.3.0 1.4.0
+31 -4
View File
@@ -7,6 +7,13 @@ Design notes that matter to the CORE and DAEMON lanes:
not used and not emitted. Parsing goes through `velox::proto::parse<T>(json)` which not used and not emitted. Parsing goes through `velox::proto::parse<T>(json)` which
returns `std::expected<T, ParseError>`, so a malformed frame from the wire is an ordinary returns `std::expected<T, ParseError>`, so a malformed frame from the wire is an ordinary
value the RPC loop handles, not a throw unwinding through the transfer path. value the RPC loop handles, not a throw unwinding through the transfer path.
* **Two error channels, kept separate.** `parse<T>() -> Result<T>` (i.e. `expected<T,
ParseError>`) is the wire failing to become typed params — always `-32602`, always
structural. A `Dispatcher::on_*` handler returns `HandlerResult<T>` (i.e. `expected<T,
HandlerError>`), which carries any contract error code plus a free-form `data` object,
so a handler can answer `-32010` `{taskId}`, `-32011` `{path}`, `-32013` `{httpStatus}`
and so on. `dispatch()` forwards the handler's code/message/data straight into the
JSON-RPC error object.
* **Serialisation is ADL `to_json`,** so `nlohmann::json j = task;` works as expected. * **Serialisation is ADL `to_json`,** so `nlohmann::json j = task;` works as expected.
Only the outbound direction is allowed to be implicit; the wire is never trusted. Only the outbound direction is allowed to be implicit; the wire is never trusted.
* **`libveloxproto`, not `libveloxcore`.** This code includes nlohmann/json, which * **`libveloxproto`, not `libveloxcore`.** This code includes nlohmann/json, which
@@ -285,16 +292,37 @@ def emit_header(c: Contract) -> str:
"nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result);", "nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result);",
"nlohmann::json make_notification(Event e, nlohmann::json params);", "nlohmann::json make_notification(Event e, nlohmann::json params);",
"", "",
"/// A handler's own failure — as opposed to ParseError, which is the wire failing",
"/// to become typed params. Carries any contract error code, a message, and a",
"/// free-form `data` object that goes straight into the JSON-RPC error's `data`",
"/// field: `{\"taskId\": ...}` for TaskNotFound, `{\"path\": ...}` for InvalidPath,",
"/// `{\"httpStatus\": ...}` for ProbeFailed. `code` defaults to InternalError so a",
"/// handler that sets only a message still produces a valid error response.",
"///",
"/// -32001/-32002/-32003 are the server layer's to raise around dispatch(), not a",
"/// handler's: they are decided before or without reference to method params.",
"struct HandlerError {",
" ErrorCode code{ErrorCode::InternalError};",
" std::string message;",
" // `= nullptr`, not `{nullptr}`: brace-init of nlohmann::json from nullptr",
" // yields the array [null], not JSON null. make_error() drops a null data.",
" nlohmann::json data = nullptr;",
"};",
"",
"template <class T>",
"using HandlerResult = std::expected<T, HandlerError>;",
"",
"/// One virtual per method. The daemon implements this; `dispatch` below does the", "/// One virtual per method. The daemon implements this; `dispatch` below does the",
"/// envelope handling, the transport check and the parameter parsing, so a handler", "/// envelope handling, the transport check and the parameter parsing, so a handler",
"/// only ever sees a validated, typed params struct.", "/// only ever sees a validated, typed params struct. Return `std::unexpected(",
"/// HandlerError{...})` to answer with a specific error code and data.",
"class Dispatcher {", "class Dispatcher {",
"public:", "public:",
" virtual ~Dispatcher() = default;", " virtual ~Dispatcher() = default;",
""] ""]
for m in c.methods: for m in c.methods:
o += doc_comment(m.doc, " ") o += doc_comment(m.doc, " ")
o.append(f" virtual Result<{cpp_type(m.result)}> {handler_name(m.name)}(const {cpp_type(m.params)}& params) = 0;") o.append(f" virtual HandlerResult<{cpp_type(m.result)}> {handler_name(m.name)}(const {cpp_type(m.params)}& params) = 0;")
o.append("") o.append("")
o += ["};", "", o += ["};", "",
"/// Parse one JSON-RPC request, route it, and return the response to write back.", "/// Parse one JSON-RPC request, route it, and return the response to write back.",
@@ -579,8 +607,7 @@ def emit_dispatch(c: Contract) -> list[str]:
' nlohmann::json{{"path", p.error().path}});', ' nlohmann::json{{"path", p.error().path}});',
f" auto r = handler.{handler_name(m.name)}(*p);", f" auto r = handler.{handler_name(m.name)}(*p);",
" if (!r)", " if (!r)",
" return make_error(id, ErrorCode::InternalError, r.error().message,", " return make_error(id, r.error().code, r.error().message, r.error().data);",
' nlohmann::json{{"path", r.error().path}});',
" nlohmann::json out = *r;", " nlohmann::json out = *r;",
" return make_result(id, std::move(out));", " return make_result(id, std::move(out));",
" }", " }",
+13 -4
View File
@@ -44,7 +44,7 @@ def main() -> int:
# velox::conformance, so the names need qualifying. # velox::conformance, so the names need qualifying.
pt, rt = "proto::" + cpp_type(m.params), "proto::" + cpp_type(m.result) pt, rt = "proto::" + cpp_type(m.params), "proto::" + cpp_type(m.result)
o += [ o += [
f" proto::Result<{rt}> {handler_name(m.name)}(const {pt}& params) override {{", f" proto::HandlerResult<{rt}> {handler_name(m.name)}(const {pt}& params) override {{",
" (void)params;", " (void)params;",
f' return golden<{rt}>("{m.name}");', f' return golden<{rt}>("{m.name}");',
" }", " }",
@@ -53,12 +53,21 @@ def main() -> int:
o += [ o += [
"private:", "private:",
" // Every fixture-backed handler only ever succeeds. A missing or unparseable",
" // fixture is a bug in the suite, not a contract outcome, so it surfaces as",
" // InternalError rather than being dressed up as a real error code.",
" template <class T>", " template <class T>",
" proto::Result<T> golden(const std::string& method) {", " proto::HandlerResult<T> golden(const std::string& method) {",
" const nlohmann::json* value = results_(method);", " const nlohmann::json* value = results_(method);",
" if (value == nullptr)", " if (value == nullptr)",
' return std::unexpected(proto::ParseError{method, "no fixture for this method"});', " return std::unexpected(proto::HandlerError{",
" return proto::parse<T>(*value, method);", ' proto::ErrorCode::InternalError, "no fixture for method " + method});',
" auto parsed = proto::parse<T>(*value, method);",
" if (!parsed)",
" return std::unexpected(proto::HandlerError{",
" proto::ErrorCode::InternalError,",
' "fixture for " + method + " failed to parse: " + parsed.error().message});',
" return std::move(*parsed);",
" }", " }",
"", "",
" std::function<const nlohmann::json*(const std::string&)> results_;", " std::function<const nlohmann::json*(const std::string&)> results_;",
@@ -18,7 +18,7 @@
"code": -32001, "code": -32001,
"message": "protocol major version mismatch: daemon speaks 1.x, client speaks 2.x", "message": "protocol major version mismatch: daemon speaks 1.x, client speaks 2.x",
"data": { "data": {
"expected": "1.0.0", "expected": "$any",
"actual": "2.0.0" "actual": "2.0.0"
} }
} }
@@ -27,7 +27,8 @@
"the connection is closed after this reply; no method is served on a mismatched major", "the connection is closed after this reply; no method is served on a mismatched major",
"a differing minor or patch is accepted, never refused", "a differing minor or patch is accepted, never refused",
"the message is safe to show a user verbatim", "the message is safe to show a user verbatim",
"the version check is transport-independent; this is replayed on the Unix socket so it is not masked by -32002" "the version check is transport-independent; this is replayed on the Unix socket so it is not masked by -32002",
"data.expected is the daemon's own current protocol version string (kProtocolVersion), not a bare major and not pinnable in a golden file -- the conformance compare on error payloads is on `code` only, structural elsewhere, so echoing the live version is fine"
], ],
"transport": "uds" "transport": "uds"
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"openrpc": "1.2.6", "openrpc": "1.2.6",
"info": { "info": {
"title": "Velox Download Manager", "title": "Velox Download Manager",
"version": "1.3.0", "version": "1.4.0",
"description": "The wire contract between veloxd and every client: the Qt GUI, the CLI, the native-messaging host and the Firefox extension. One JSON-RPC 2.0 payload set over four framings; only the framing differs.\n\nGENERATED from contracts/schema/ by contracts/codegen/gen_openrpc.py. Do not edit by hand.", "description": "The wire contract between veloxd and every client: the Qt GUI, the CLI, the native-messaging host and the Firefox extension. One JSON-RPC 2.0 payload set over four framings; only the framing differs.\n\nGENERATED from contracts/schema/ by contracts/codegen/gen_openrpc.py. Do not edit by hand.",
"license": { "license": {
"name": "See repository LICENSE" "name": "See repository LICENSE"
+78
View File
@@ -0,0 +1,78 @@
# PROTO → DAEMON — answers to `daemon/docs/proto-requests-m1.md`
Status: **answered**. Against `contracts/` at **1.4.0** (`lane/proto`).
Raised by DAEMON while building `rpc/` against 1.3.0.
---
## P1 — the generated `Dispatcher` has no error channel below `-32603` · **landed in 1.4.0**
Done, essentially as sketched. The generated C++ binding now has two error channels,
kept deliberately separate:
| Channel | Type | Raised by | Always |
|---|---|---|---|
| parse | `Result<T>` = `expected<T, ParseError>` | `dispatch()` turning the wire into typed params | `-32602`, structural, `data.path` a JSON pointer |
| handler | `HandlerResult<T>` = `expected<T, HandlerError>` | a `Dispatcher::on_*` method | any contract code + free-form `data` |
```cpp
struct HandlerError {
ErrorCode code{ErrorCode::InternalError}; // default: a bare HandlerError{} is a valid -32603
std::string message;
nlohmann::json data = nullptr; // forwarded straight into the JSON-RPC error's data
};
template <class T> using HandlerResult = std::expected<T, HandlerError>;
```
Every `Dispatcher::on_*` now returns `HandlerResult<T>`. `dispatch()`'s handler-error
branch went from a hard-coded `InternalError` to:
```cpp
if (!r) return make_error(id, r.error().code, r.error().message, r.error().data);
```
So the three in-handler fixtures are now satisfiable by a conformant server:
| Fixture | `return std::unexpected(HandlerError{ ... })` |
|---|---|
| `download.get.not-found` | `ErrorCode::TaskNotFound, "no such task", {{"taskId", id}}` |
| `download.add.invalid-path` | `ErrorCode::InvalidPath, "outside allowed roots", {{"path", p}}` |
| `download.probe.probe-failed` | `ErrorCode::ProbeFailed, "HTTP 403", {{"httpStatus", 403}}` |
`session.pair.rate-limited` (`-32014`) is a handler result too if you want it there —
nothing stops a handler returning `HandlerError{ErrorCode::RateLimited, ...,
{{"retryAfterSec", 60}}}`. `-32001/-32002/-32003` stay yours to raise in the server layer
around `dispatch()`, as you're already doing; they're decided before or without reference
to method params, and `HandlerError`'s own doc comment says so.
Verified end to end: a handler returning each of the above through the real `dispatch()`
path produces the right code with the `data` payload intact, and a bare `HandlerError{}`
still yields a clean `-32603` with no `data` field. (Watch the nlohmann brace-init trap:
`HandlerError{code, msg, {{"k", v}}}` gives an object, but a lone `{nullptr}` would give
the array `[null]` — the struct's member initializer is `= nullptr` for exactly that
reason.)
`FixtureDispatcher` and `conformance_main.cpp`: the generated dispatcher swapped
`Result``HandlerResult` automatically; `conformance_main.cpp` only ever inspects
`dispatch()`'s JSON output and needed no change.
**Version:** minor, 1.3.0 → 1.4.0. The wire is byte-identical — no schema, fixture, or
OpenRPC change — but every implementer of `Dispatcher` must swap `Result``HandlerResult`
on their `on_*` overrides or they won't compile, and a version bump is how lanes are told
to regenerate and adapt. Not major and not an ADR: one lane consumes this binding, it's
the lane that asked, and there's no contested design here — the shape is the one you
proposed. `kProtocolVersion` moves to `"1.4.0"` with it.
## P2 — clarifications
**`session.hello.version-mismatch` `data.expected`.** You're right that `"1.0.0"` in the
fixture is stale. Fixed: it's now `$any`. Echo `kProtocolVersion` (`"1.4.0"`) there — the
conformance compare on an error fixture is on `code` only, structural elsewhere, so the
live version string is fine and can't be pinned in a golden file that outlives version
bumps anyway. `actual` stays the concrete bad version the fake client sent (`"2.0.0"`).
**`SessionHelloResult.transport` always populated.** No change requested, noted. The
field's own description already invites it (`"Lets a client know up front which privileged
methods will be refused"`), so always setting `"uds"` / `"ws"` is using it as intended.
`std::optional` stays because a hand-rolled or older server may legitimately omit it and a
client must tolerate that.
+40 -79
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_cpp.py // Generator: contracts/codegen/gen_cpp.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_cpp.py // python3 contracts/codegen/gen_cpp.py
@@ -7245,8 +7245,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_capture_getRules(*p); auto r = handler.on_capture_getRules(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7257,8 +7256,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_capture_offer(*p); auto r = handler.on_capture_offer(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7269,8 +7267,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_category_list(*p); auto r = handler.on_category_list(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7281,8 +7278,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_category_remove(*p); auto r = handler.on_category_remove(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7293,8 +7289,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_category_upsert(*p); auto r = handler.on_category_upsert(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7305,8 +7300,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_add(*p); auto r = handler.on_download_add(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7317,8 +7311,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_addBatch(*p); auto r = handler.on_download_addBatch(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7329,8 +7322,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_cancel(*p); auto r = handler.on_download_cancel(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7341,8 +7333,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_get(*p); auto r = handler.on_download_get(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7353,8 +7344,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_list(*p); auto r = handler.on_download_list(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7365,8 +7355,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_pause(*p); auto r = handler.on_download_pause(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7377,8 +7366,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_probe(*p); auto r = handler.on_download_probe(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7389,8 +7377,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_provideAuth(*p); auto r = handler.on_download_provideAuth(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7401,8 +7388,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_refreshUrl(*p); auto r = handler.on_download_refreshUrl(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7413,8 +7399,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_remove(*p); auto r = handler.on_download_remove(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7425,8 +7410,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_resume(*p); auto r = handler.on_download_resume(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7437,8 +7421,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_start(*p); auto r = handler.on_download_start(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7449,8 +7432,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_download_update(*p); auto r = handler.on_download_update(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7461,8 +7443,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_grabber_harvest(*p); auto r = handler.on_grabber_harvest(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7473,8 +7454,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_grabber_start(*p); auto r = handler.on_grabber_start(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7485,8 +7465,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_grabber_status(*p); auto r = handler.on_grabber_status(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7497,8 +7476,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_limiter_get(*p); auto r = handler.on_limiter_get(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7509,8 +7487,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_limiter_set(*p); auto r = handler.on_limiter_set(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7521,8 +7498,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_media_addVariant(*p); auto r = handler.on_media_addVariant(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7533,8 +7509,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_media_listVariants(*p); auto r = handler.on_media_listVariants(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7545,8 +7520,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_queue_list(*p); auto r = handler.on_queue_list(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7557,8 +7531,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_queue_reorder(*p); auto r = handler.on_queue_reorder(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7569,8 +7542,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_queue_start(*p); auto r = handler.on_queue_start(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7581,8 +7553,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_queue_stop(*p); auto r = handler.on_queue_stop(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7593,8 +7564,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_queue_upsert(*p); auto r = handler.on_queue_upsert(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7605,8 +7575,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_rules_list(*p); auto r = handler.on_rules_list(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7617,8 +7586,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_rules_upsert(*p); auto r = handler.on_rules_upsert(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7629,8 +7597,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_schedule_get(*p); auto r = handler.on_schedule_get(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7641,8 +7608,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_schedule_set(*p); auto r = handler.on_schedule_set(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7653,8 +7619,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_session_hello(*p); auto r = handler.on_session_hello(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7665,8 +7630,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_session_pair(*p); auto r = handler.on_session_pair(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7677,8 +7641,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_session_subscribe(*p); auto r = handler.on_session_subscribe(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7689,8 +7652,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_settings_get(*p); auto r = handler.on_settings_get(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
@@ -7701,8 +7663,7 @@ nlohmann::json dispatch(Dispatcher& handler, Transport transport, const nlohmann
nlohmann::json{{"path", p.error().path}}); nlohmann::json{{"path", p.error().path}});
auto r = handler.on_settings_set(*p); auto r = handler.on_settings_set(*p);
if (!r) if (!r)
return make_error(id, ErrorCode::InternalError, r.error().message, return make_error(id, r.error().code, r.error().message, r.error().data);
nlohmann::json{{"path", r.error().path}});
nlohmann::json out = *r; nlohmann::json out = *r;
return make_result(id, std::move(out)); return make_result(id, std::move(out));
} }
+63 -42
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_cpp.py // Generator: contracts/codegen/gen_cpp.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_cpp.py // python3 contracts/codegen/gen_cpp.py
@@ -27,7 +27,7 @@
// docs/adr/0009-generated-protocol-library.md. // docs/adr/0009-generated-protocol-library.md.
namespace velox::proto { namespace velox::proto {
inline constexpr std::string_view kProtocolVersion = "1.3.0"; inline constexpr std::string_view kProtocolVersion = "1.4.0";
/// Why a payload could not be turned into a typed value. `path` is a JSON Pointer /// Why a payload could not be turned into a typed value. `path` is a JSON Pointer
/// into the offending document, so a conformance failure names the exact field. /// into the offending document, so a conformance failure names the exact field.
@@ -1785,9 +1785,30 @@ nlohmann::json make_error(const nlohmann::json& id, ErrorCode code, std::string_
nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result); nlohmann::json make_result(const nlohmann::json& id, nlohmann::json result);
nlohmann::json make_notification(Event e, nlohmann::json params); nlohmann::json make_notification(Event e, nlohmann::json params);
/// A handler's own failure — as opposed to ParseError, which is the wire failing
/// to become typed params. Carries any contract error code, a message, and a
/// free-form `data` object that goes straight into the JSON-RPC error's `data`
/// field: `{"taskId": ...}` for TaskNotFound, `{"path": ...}` for InvalidPath,
/// `{"httpStatus": ...}` for ProbeFailed. `code` defaults to InternalError so a
/// handler that sets only a message still produces a valid error response.
///
/// -32001/-32002/-32003 are the server layer's to raise around dispatch(), not a
/// handler's: they are decided before or without reference to method params.
struct HandlerError {
ErrorCode code{ErrorCode::InternalError};
std::string message;
// `= nullptr`, not `{nullptr}`: brace-init of nlohmann::json from nullptr
// yields the array [null], not JSON null. make_error() drops a null data.
nlohmann::json data = nullptr;
};
template <class T>
using HandlerResult = std::expected<T, HandlerError>;
/// One virtual per method. The daemon implements this; `dispatch` below does the /// One virtual per method. The daemon implements this; `dispatch` below does the
/// envelope handling, the transport check and the parameter parsing, so a handler /// envelope handling, the transport check and the parameter parsing, so a handler
/// only ever sees a validated, typed params struct. /// only ever sees a validated, typed params struct. Return `std::unexpected(
/// HandlerError{...})` to answer with a specific error code and data.
class Dispatcher { class Dispatcher {
public: public:
virtual ~Dispatcher() = default; virtual ~Dispatcher() = default;
@@ -1795,61 +1816,61 @@ public:
/// The daemon's capture policy, so the extension's shouldCapture decision cannot drift from the /// The daemon's capture policy, so the extension's shouldCapture decision cannot drift from the
/// daemon's. Fetched on connect and whenever event.settings.changed names a capture.* key. If /// daemon's. Fetched on connect and whenever event.settings.changed names a capture.* key. If
/// this call fails the extension keeps its last known rules and stays fail-open. /// this call fails the extension keeps its last known rules and stays fail-open.
virtual Result<CaptureRules> on_capture_getRules(const CaptureGetRulesParams& params) = 0; virtual HandlerResult<CaptureRules> on_capture_getRules(const CaptureGetRulesParams& params) = 0;
/// Firefox offers an intercepted response to the daemon. The daemon MUST reply within 750 ms; /// Firefox offers an intercepted response to the daemon. The daemon MUST reply within 750 ms;
/// the extension abandons the offer and lets Firefox download normally on timeout. This /// the extension abandons the offer and lets Firefox download normally on timeout. This
/// deadline is the whole reason capture fails open, and it is conformance-tested: a daemon that /// deadline is the whole reason capture fails open, and it is conformance-tested: a daemon that
/// is slow, down, or erroring must never cost the user a download. /// is slow, down, or erroring must never cost the user a download.
virtual Result<CaptureOfferResult> on_capture_offer(const CaptureOfferParams& params) = 0; virtual HandlerResult<CaptureOfferResult> on_capture_offer(const CaptureOfferParams& params) = 0;
/// Every category with its folder and extension list. The extension calls this to populate its /// Every category with its folder and extension list. The extension calls this to populate its
/// default-category picker, which is why it is not privileged; it is read-only and exposes only /// default-category picker, which is why it is not privileged; it is read-only and exposes only
/// paths the user already configured. /// paths the user already configured.
virtual Result<CategoryListResult> on_category_list(const CategoryListParams& params) = 0; virtual HandlerResult<CategoryListResult> on_category_list(const CategoryListParams& params) = 0;
/// Delete a user-created category. Built-in categories are refused with -32602. Tasks filed /// Delete a user-created category. Built-in categories are refused with -32602. Tasks filed
/// under it are reassigned to reassignTo, or to the default category when that is null; no task /// under it are reassigned to reassignTo, or to the default category when that is null; no task
/// is ever orphaned. /// is ever orphaned.
virtual Result<CategoryRemoveResult> on_category_remove(const CategoryRemoveParams& params) = 0; virtual HandlerResult<CategoryRemoveResult> on_category_remove(const CategoryRemoveParams& params) = 0;
/// Create or replace a category. Omit categoryId to create; supply it to replace. Changing /// Create or replace a category. Omit categoryId to create; supply it to replace. Changing
/// saveDir does not move existing files — the GUI asks separately and issues download.update /// saveDir does not move existing files — the GUI asks separately and issues download.update
/// per task, so a re-point is never a surprise mass file move. /// per task, so a re-point is never a surprise mass file move.
virtual Result<CategoryUpsertResult> on_category_upsert(const CategoryUpsertParams& params) = 0; virtual HandlerResult<CategoryUpsertResult> on_category_upsert(const CategoryUpsertParams& params) = 0;
/// Create one task. saveDir is canonicalized and checked against saveTo.allowedRoots before /// Create one task. saveDir is canonicalized and checked against saveTo.allowedRoots before
/// anything is written; a path that escapes them is refused with -32011 and no file is created. /// anything is written; a path that escapes them is refused with -32011 and no file is created.
virtual Result<DownloadAddResult> on_download_add(const DownloadSpec& params) = 0; virtual HandlerResult<DownloadAddResult> on_download_add(const DownloadSpec& params) = 0;
/// Create many tasks in one call: the clipboard blob, the wildcard expander, and the /// Create many tasks in one call: the clipboard blob, the wildcard expander, and the
/// extension's 'Download all links'. Partial success is normal and is reported per item rather /// extension's 'Download all links'. Partial success is normal and is reported per item rather
/// than failing the whole batch. /// than failing the whole batch.
virtual Result<DownloadAddBatchResult> on_download_addBatch(const DownloadAddBatchParams& params) = 0; virtual HandlerResult<DownloadAddBatchResult> on_download_addBatch(const DownloadAddBatchParams& params) = 0;
/// Stop the given tasks and mark them cancelled. The .veloxpart file is kept so the user can /// Stop the given tasks and mark them cancelled. The .veloxpart file is kept so the user can
/// still resume from the list; download.remove is what deletes bytes. /// still resume from the list; download.remove is what deletes bytes.
virtual Result<BulkTaskResult> on_download_cancel(const DownloadCancelParams& params) = 0; virtual HandlerResult<BulkTaskResult> on_download_cancel(const DownloadCancelParams& params) = 0;
/// Full detail for one task, including per-segment state. Backs the progress dialog. Poll it no /// Full detail for one task, including per-segment state. Backs the progress dialog. Poll it no
/// faster than the progress dialog repaints; the table must use events instead. /// faster than the progress dialog repaints; the table must use events instead.
virtual Result<TaskDetail> on_download_get(const DownloadGetParams& params) = 0; virtual HandlerResult<TaskDetail> on_download_get(const DownloadGetParams& params) = 0;
/// The main table. Filtering, sorting and paging all happen in the daemon so the GUI never /// The main table. Filtering, sorting and paging all happen in the daemon so the GUI never
/// materializes 100k rows to show 40. Called once on connect; after that the table is /// materializes 100k rows to show 40. Called once on connect; after that the table is
/// maintained from events, never re-fetched on a progress tick. /// maintained from events, never re-fetched on a progress tick.
virtual Result<DownloadListResult> on_download_list(const DownloadListParams& params) = 0; virtual HandlerResult<DownloadListResult> on_download_list(const DownloadListParams& params) = 0;
/// Suspend transfers and flush every segment's progress to the .veloxpart.meta file, so a pause /// Suspend transfers and flush every segment's progress to the .veloxpart.meta file, so a pause
/// is indistinguishable from a crash as far as resume is concerned. Never loses bytes already /// is indistinguishable from a crash as far as resume is concerned. Never loses bytes already
/// written. /// written.
virtual Result<BulkTaskResult> on_download_pause(const DownloadPauseParams& params) = 0; virtual HandlerResult<BulkTaskResult> on_download_pause(const DownloadPauseParams& params) = 0;
/// Ask what is at a URL without creating a task. Populates the File Info dialog. Runs a HEAD, /// Ask what is at a URL without creating a task. Populates the File Info dialog. Runs a HEAD,
/// falling back to a ranged GET when HEAD is refused, which is also how resumability is /// falling back to a ranged GET when HEAD is refused, which is also how resumability is
/// established. Never blocks the RPC loop; the dialog opens immediately and fills in when this /// established. Never blocks the RPC loop; the dialog opens immediately and fills in when this
/// lands. /// lands.
virtual Result<DownloadProbeResult> on_download_probe(const DownloadProbeParams& params) = 0; virtual HandlerResult<DownloadProbeResult> on_download_probe(const DownloadProbeParams& params) = 0;
/// Answer an event.auth.required challenge. The task sits in retry_wait until this arrives; on /// Answer an event.auth.required challenge. The task sits in retry_wait until this arrives; on
/// success the daemon retries with the credentials attached and the task resumes on its own — /// success the daemon retries with the credentials attached and the task resumes on its own —
@@ -1859,137 +1880,137 @@ public:
/// into a log') — this is the other half of that promise. Credentials are handed to the Secret /// into a log') — this is the other half of that promise. Credentials are handed to the Secret
/// Service, never to SQLite and never logged; save only tells the daemon whether to persist /// Service, never to SQLite and never logged; save only tells the daemon whether to persist
/// them there for next time, or use them for this attempt alone. /// them there for next time, or use them for this attempt alone.
virtual Result<DownloadProvideAuthResult> on_download_provideAuth(const DownloadProvideAuthParams& params) = 0; virtual HandlerResult<DownloadProvideAuthResult> on_download_provideAuth(const DownloadProvideAuthParams& params) = 0;
/// IDM's 'Refresh Download Address'. Point an existing task at a freshly-issued URL when a /// IDM's 'Refresh Download Address'. Point an existing task at a freshly-issued URL when a
/// signed link has expired, keeping every byte already on disk. The daemon re-probes and /// signed link has expired, keeping every byte already on disk. The daemon re-probes and
/// compares size and validator: if they still match, the transfer resumes from where it /// compares size and validator: if they still match, the transfer resumes from where it
/// stopped; if they do not, it says so rather than silently restarting. /// stopped; if they do not, it says so rather than silently restarting.
virtual Result<DownloadRefreshUrlResult> on_download_refreshUrl(const DownloadRefreshUrlParams& params) = 0; virtual HandlerResult<DownloadRefreshUrlResult> on_download_refreshUrl(const DownloadRefreshUrlParams& params) = 0;
/// Drop tasks from the list, optionally deleting the bytes on disk. Privileged: this is the /// Drop tasks from the list, optionally deleting the bytes on disk. Privileged: this is the
/// only method that destroys user data, and the extension is never allowed to reach it. The /// only method that destroys user data, and the extension is never allowed to reach it. The
/// daemon deletes the .veloxpart and .veloxpart.meta pair, and the finished file only when /// daemon deletes the .veloxpart and .veloxpart.meta pair, and the finished file only when
/// deleteFile is true. /// deleteFile is true.
virtual Result<DownloadRemoveResult> on_download_remove(const DownloadRemoveParams& params) = 0; virtual HandlerResult<DownloadRemoveResult> on_download_remove(const DownloadRemoveParams& params) = 0;
/// Continue paused tasks. Resumption is revalidated with If-Range against the stored ETag or /// Continue paused tasks. Resumption is revalidated with If-Range against the stored ETag or
/// Last-Modified; a 200 where 206 was expected means the file changed on the server, and the /// Last-Modified; a 200 where 206 was expected means the file changed on the server, and the
/// task moves to failed with a clear error rather than corrupting the part file. /// task moves to failed with a clear error rather than corrupting the part file.
virtual Result<BulkTaskResult> on_download_resume(const DownloadResumeParams& params) = 0; virtual HandlerResult<BulkTaskResult> on_download_resume(const DownloadResumeParams& params) = 0;
/// Begin or restart the given tasks. A task in 'queued' jumps its queue; a task already /// Begin or restart the given tasks. A task in 'queued' jumps its queue; a task already
/// downloading is a no-op reported as changed false. /// downloading is a no-op reported as changed false.
virtual Result<BulkTaskResult> on_download_start(const DownloadStartParams& params) = 0; virtual HandlerResult<BulkTaskResult> on_download_start(const DownloadStartParams& params) = 0;
/// Change a task's mutable fields. Moving saveDir or filename moves the file on disk in the /// Change a task's mutable fields. Moving saveDir or filename moves the file on disk in the
/// same operation, which is what makes dragging a row onto a category work as one RPC. /// same operation, which is what makes dragging a row onto a category work as one RPC.
/// Privileged: it can name a destination path. /// Privileged: it can name a destination path.
virtual Result<TaskSummary> on_download_update(const DownloadUpdateParams& params) = 0; virtual HandlerResult<TaskSummary> on_download_update(const DownloadUpdateParams& params) = 0;
/// Turn selected crawl results into tasks. This is the only grabber call that creates /// Turn selected crawl results into tasks. This is the only grabber call that creates
/// downloads, and it names exactly the files the user ticked — a crawl never starts a download /// downloads, and it names exactly the files the user ticked — a crawl never starts a download
/// on its own. /// on its own.
virtual Result<GrabberHarvestResult> on_grabber_harvest(const GrabberHarvestParams& params) = 0; virtual HandlerResult<GrabberHarvestResult> on_grabber_harvest(const GrabberHarvestParams& params) = 0;
/// Start a depth-limited crawl. Nothing is downloaded by this call: it only walks pages and /// Start a depth-limited crawl. Nothing is downloaded by this call: it only walks pages and
/// collects candidate links, which the wizard then shows for selection. Privileged because an /// collects candidate links, which the wizard then shows for selection. Privileged because an
/// unbounded crawl is a resource commitment the browser must not be able to make on the user's /// unbounded crawl is a resource commitment the browser must not be able to make on the user's
/// behalf. /// behalf.
virtual Result<GrabberStartResult> on_grabber_start(const GrabberStartParams& params) = 0; virtual HandlerResult<GrabberStartResult> on_grabber_start(const GrabberStartParams& params) = 0;
/// Poll one crawl. Also delivered as event.grabber.progress; the poll exists so the wizard can /// Poll one crawl. Also delivered as event.grabber.progress; the poll exists so the wizard can
/// be reopened on a job it did not start and still catch up. /// be reopened on a job it did not start and still catch up.
virtual Result<GrabberStatusResult> on_grabber_status(const GrabberStatusParams& params) = 0; virtual HandlerResult<GrabberStatusResult> on_grabber_status(const GrabberStatusParams& params) = 0;
/// Current global speed limit. Privileged: changing or reading the limiter belongs to the GUI /// Current global speed limit. Privileged: changing or reading the limiter belongs to the GUI
/// and CLI; the extension shows throughput from event.speed.global instead. /// and CLI; the extension shows throughput from event.speed.global instead.
virtual Result<Limiter> on_limiter_get(const LimiterGetParams& params) = 0; virtual HandlerResult<Limiter> on_limiter_get(const LimiterGetParams& params) = 0;
/// Set the global token-bucket limit. With applyToRunning true the change re-tunes transfers /// Set the global token-bucket limit. With applyToRunning true the change re-tunes transfers
/// already in flight instead of taking effect only on the next task — the Speed Limiter /// already in flight instead of taking effect only on the next task — the Speed Limiter
/// window's 'apply now' button. /// window's 'apply now' button.
virtual Result<Limiter> on_limiter_set(const Limiter& params) = 0; virtual HandlerResult<Limiter> on_limiter_set(const Limiter& params) = 0;
/// Turn one enumerated variant into a task. The daemon fetches the segments in parallel and /// Turn one enumerated variant into a task. The daemon fetches the segments in parallel and
/// muxes them with ffmpeg; the result is an ordinary task that appears in the list like any /// muxes them with ffmpeg; the result is an ordinary task that appears in the list like any
/// other download. Refused with -32602 when the variant is DRM-protected. /// other download. Refused with -32602 when the variant is DRM-protected.
virtual Result<MediaAddVariantResult> on_media_addVariant(const MediaAddVariantParams& params) = 0; virtual HandlerResult<MediaAddVariantResult> on_media_addVariant(const MediaAddVariantParams& params) = 0;
/// Parse an HLS or DASH manifest in the daemon and enumerate its renditions. The extension /// Parse an HLS or DASH manifest in the daemon and enumerate its renditions. The extension
/// never parses a manifest — that logic lives in one language, in one place. Variants with drm /// never parses a manifest — that logic lives in one language, in one place. Variants with drm
/// true are reported so the UI can grey them out; DRM-protected streams are refused, not /// true are reported so the UI can grey them out; DRM-protected streams are refused, not
/// attempted. /// attempted.
virtual Result<MediaListVariantsResult> on_media_listVariants(const MediaListVariantsParams& params) = 0; virtual HandlerResult<MediaListVariantsResult> on_media_listVariants(const MediaListVariantsParams& params) = 0;
/// Every queue with its run state and ordering. Not privileged: the extension's 'Add to Queue' /// Every queue with its run state and ordering. Not privileged: the extension's 'Add to Queue'
/// picker needs it. /// picker needs it.
virtual Result<QueueListResult> on_queue_list(const QueueListParams& params) = 0; virtual HandlerResult<QueueListResult> on_queue_list(const QueueListParams& params) = 0;
/// Rewrite a queue's run order. taskIds must be a permutation of the queue's current /// Rewrite a queue's run order. taskIds must be a permutation of the queue's current
/// membership; anything else is -32602 rather than a partial reorder, so a stale drag from an /// membership; anything else is -32602 rather than a partial reorder, so a stale drag from an
/// out-of-date view cannot quietly reshuffle the queue. /// out-of-date view cannot quietly reshuffle the queue.
virtual Result<QueueReorderResult> on_queue_reorder(const QueueReorderParams& params) = 0; virtual HandlerResult<QueueReorderResult> on_queue_reorder(const QueueReorderParams& params) = 0;
/// Start a queue running. The scheduler then admits up to maxConcurrent tasks from it, in /// Start a queue running. The scheduler then admits up to maxConcurrent tasks from it, in
/// order, and keeps that many running until the queue drains or is stopped. /// order, and keeps that many running until the queue drains or is stopped.
virtual Result<QueueStartResult> on_queue_start(const QueueStartParams& params) = 0; virtual HandlerResult<QueueStartResult> on_queue_start(const QueueStartParams& params) = 0;
/// Stop admitting new tasks from a queue. Tasks already running are paused when pauseRunning is /// Stop admitting new tasks from a queue. Tasks already running are paused when pauseRunning is
/// true, and otherwise allowed to finish — the difference between 'stop the queue' and 'stop /// true, and otherwise allowed to finish — the difference between 'stop the queue' and 'stop
/// everything', which IDM conflates and users trip over. /// everything', which IDM conflates and users trip over.
virtual Result<QueueStopResult> on_queue_stop(const QueueStopParams& params) = 0; virtual HandlerResult<QueueStopResult> on_queue_stop(const QueueStopParams& params) = 0;
/// Create or replace a queue, including its schedule and concurrency cap. Omit queueId to /// Create or replace a queue, including its schedule and concurrency cap. Omit queueId to
/// create. taskIds in the payload is ignored — membership changes through download.update and /// create. taskIds in the payload is ignored — membership changes through download.update and
/// queue.reorder so that two clients editing at once cannot silently drop a task. /// queue.reorder so that two clients editing at once cannot silently drop a task.
virtual Result<QueueUpsertResult> on_queue_upsert(const QueueUpsertParams& params) = 0; virtual HandlerResult<QueueUpsertResult> on_queue_upsert(const QueueUpsertParams& params) = 0;
/// The rules engine's table, in priority order. Privileged: these are the daemon's routing /// The rules engine's table, in priority order. Privileged: these are the daemon's routing
/// policy. The extension gets its own narrowed view through capture.getRules instead. /// policy. The extension gets its own narrowed view through capture.getRules instead.
virtual Result<RulesListResult> on_rules_list(const RulesListParams& params) = 0; virtual HandlerResult<RulesListResult> on_rules_list(const RulesListParams& params) = 0;
/// Create, replace, or delete rules in one atomic write. 'upsert' carries the rules to store /// Create, replace, or delete rules in one atomic write. 'upsert' carries the rules to store
/// and 'remove' the ruleIds to drop; applying both at once means a reprioritisation never /// and 'remove' the ruleIds to drop; applying both at once means a reprioritisation never
/// leaves the table in a half-valid state. /// leaves the table in a half-valid state.
virtual Result<RulesUpsertResult> on_rules_upsert(const RulesUpsertParams& params) = 0; virtual HandlerResult<RulesUpsertResult> on_rules_upsert(const RulesUpsertParams& params) = 0;
/// The schedule for one queue, or every schedule when queueId is null. Backs the Scheduler /// The schedule for one queue, or every schedule when queueId is null. Backs the Scheduler
/// window. /// window.
virtual Result<ScheduleGetResult> on_schedule_get(const ScheduleGetParams& params) = 0; virtual HandlerResult<ScheduleGetResult> on_schedule_get(const ScheduleGetParams& params) = 0;
/// Set or clear a queue's schedule. A null schedule clears it and leaves the queue under manual /// Set or clear a queue's schedule. A null schedule clears it and leaves the queue under manual
/// control. Times are local wall-clock and are re-evaluated on a DST change rather than being /// control. Times are local wall-clock and are re-evaluated on a DST change rather than being
/// resolved to absolute instants at set time. /// resolved to absolute instants at set time.
virtual Result<ScheduleSetResult> on_schedule_set(const ScheduleSetParams& params) = 0; virtual HandlerResult<ScheduleSetResult> on_schedule_set(const ScheduleSetParams& params) = 0;
/// First call on every connection, on every transport. The daemon compares protocolVersion /// First call on every connection, on every transport. The daemon compares protocolVersion
/// majors and refuses a mismatch with -32001 so a stale GUI or extension fails loudly on /// majors and refuses a mismatch with -32001 so a stale GUI or extension fails loudly on
/// connect instead of subtly at the tenth field. On the WebSocket transport a valid token is /// connect instead of subtly at the tenth field. On the WebSocket transport a valid token is
/// required unless the client is about to call session.pair. /// required unless the client is about to call session.pair.
virtual Result<SessionHelloResult> on_session_hello(const SessionHelloParams& params) = 0; virtual HandlerResult<SessionHelloResult> on_session_hello(const SessionHelloParams& params) = 0;
/// WebSocket transport only. Triggers a GUI or desktop-notification prompt showing a four-digit /// WebSocket transport only. Triggers a GUI or desktop-notification prompt showing a four-digit
/// code; the user must approve before a token is issued. Failed attempts are rate-limited to /// code; the user must approve before a token is issued. Failed attempts are rate-limited to
/// 5/min followed by a 60 s lockout (-32014) so a token cannot be brute-forced by another local /// 5/min followed by a 60 s lockout (-32014) so a token cannot be brute-forced by another local
/// process. The daemon stores only a hash of the token. /// process. The daemon stores only a hash of the token.
virtual Result<SessionPairResult> on_session_pair(const SessionPairParams& params) = 0; virtual HandlerResult<SessionPairResult> on_session_pair(const SessionPairParams& params) = 0;
/// Choose which notifications this connection receives. Subscribing replaces the previous /// Choose which notifications this connection receives. Subscribing replaces the previous
/// selection rather than adding to it, so a client can narrow its firehose without /// selection rather than adding to it, so a client can narrow its firehose without
/// reconnecting. Nothing is delivered until this is called. /// reconnecting. Nothing is delivered until this is called.
virtual Result<SessionSubscribeResult> on_session_subscribe(const SessionSubscribeParams& params) = 0; virtual HandlerResult<SessionSubscribeResult> on_session_subscribe(const SessionSubscribeParams& params) = 0;
/// Read settings. keys null means everything. Privileged: the settings bag names local /// Read settings. keys null means everything. Privileged: the settings bag names local
/// filesystem paths and the allowed write roots, which the extension has no business /// filesystem paths and the allowed write roots, which the extension has no business
/// enumerating — it gets capture.getRules instead. /// enumerating — it gets capture.getRules instead.
virtual Result<SettingsGetResult> on_settings_get(const SettingsGetParams& params) = 0; virtual HandlerResult<SettingsGetResult> on_settings_get(const SettingsGetParams& params) = 0;
/// Write settings. Only the keys present in values change. Rejected with -32602 if a key is /// Write settings. Only the keys present in values change. Rejected with -32602 if a key is
/// unknown or a value fails the Settings schema, and with -32011 if a directory key names a /// unknown or a value fails the Settings schema, and with -32011 if a directory key names a
/// path that cannot be written. Emits event.settings.changed with exactly the keys that took /// path that cannot be written. Emits event.settings.changed with exactly the keys that took
/// effect. /// effect.
virtual Result<SettingsSetResult> on_settings_set(const SettingsSetParams& params) = 0; virtual HandlerResult<SettingsSetResult> on_settings_set(const SettingsSetParams& params) = 0;
}; };
+1 -1
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_ts.py // Generator: contracts/codegen/gen_ts.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_ts.py // python3 contracts/codegen/gen_ts.py
+1 -1
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_ts.py // Generator: contracts/codegen/gen_ts.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_ts.py // python3 contracts/codegen/gen_ts.py
+1 -1
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_ts.py // Generator: contracts/codegen/gen_ts.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_ts.py // python3 contracts/codegen/gen_ts.py
+2 -2
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_ts.py // Generator: contracts/codegen/gen_ts.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_ts.py // python3 contracts/codegen/gen_ts.py
@@ -11,7 +11,7 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export const PROTOCOL_VERSION = "1.3.0"; export const PROTOCOL_VERSION = "1.4.0";
/** /**
* Every error code the daemon may return. Adding one is a minor bump; changing the meaning * Every error code the daemon may return. Adding one is a minor bump; changing the meaning
+1 -1
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_ts.py // Generator: contracts/codegen/gen_ts.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_ts.py // python3 contracts/codegen/gen_ts.py
+52 -43
View File
@@ -3,7 +3,7 @@
// //
// Source: contracts/schema/** // Source: contracts/schema/**
// Generator: contracts/codegen/gen_cpp.py // Generator: contracts/codegen/gen_cpp.py
// Contract: v1.3.0 // Contract: v1.4.0
// //
// Hand-editing this file is a merge blocker. Fix the schema and regenerate: // Hand-editing this file is a merge blocker. Fix the schema and regenerate:
// python3 contracts/codegen/gen_cpp.py // python3 contracts/codegen/gen_cpp.py
@@ -27,208 +27,217 @@ public:
explicit FixtureDispatcher(std::function<const nlohmann::json*(const std::string&)> results) explicit FixtureDispatcher(std::function<const nlohmann::json*(const std::string&)> results)
: results_(std::move(results)) {} : results_(std::move(results)) {}
proto::Result<proto::CaptureRules> on_capture_getRules(const proto::CaptureGetRulesParams& params) override { proto::HandlerResult<proto::CaptureRules> on_capture_getRules(const proto::CaptureGetRulesParams& params) override {
(void)params; (void)params;
return golden<proto::CaptureRules>("capture.getRules"); return golden<proto::CaptureRules>("capture.getRules");
} }
proto::Result<proto::CaptureOfferResult> on_capture_offer(const proto::CaptureOfferParams& params) override { proto::HandlerResult<proto::CaptureOfferResult> on_capture_offer(const proto::CaptureOfferParams& params) override {
(void)params; (void)params;
return golden<proto::CaptureOfferResult>("capture.offer"); return golden<proto::CaptureOfferResult>("capture.offer");
} }
proto::Result<proto::CategoryListResult> on_category_list(const proto::CategoryListParams& params) override { proto::HandlerResult<proto::CategoryListResult> on_category_list(const proto::CategoryListParams& params) override {
(void)params; (void)params;
return golden<proto::CategoryListResult>("category.list"); return golden<proto::CategoryListResult>("category.list");
} }
proto::Result<proto::CategoryRemoveResult> on_category_remove(const proto::CategoryRemoveParams& params) override { proto::HandlerResult<proto::CategoryRemoveResult> on_category_remove(const proto::CategoryRemoveParams& params) override {
(void)params; (void)params;
return golden<proto::CategoryRemoveResult>("category.remove"); return golden<proto::CategoryRemoveResult>("category.remove");
} }
proto::Result<proto::CategoryUpsertResult> on_category_upsert(const proto::CategoryUpsertParams& params) override { proto::HandlerResult<proto::CategoryUpsertResult> on_category_upsert(const proto::CategoryUpsertParams& params) override {
(void)params; (void)params;
return golden<proto::CategoryUpsertResult>("category.upsert"); return golden<proto::CategoryUpsertResult>("category.upsert");
} }
proto::Result<proto::DownloadAddResult> on_download_add(const proto::DownloadSpec& params) override { proto::HandlerResult<proto::DownloadAddResult> on_download_add(const proto::DownloadSpec& params) override {
(void)params; (void)params;
return golden<proto::DownloadAddResult>("download.add"); return golden<proto::DownloadAddResult>("download.add");
} }
proto::Result<proto::DownloadAddBatchResult> on_download_addBatch(const proto::DownloadAddBatchParams& params) override { proto::HandlerResult<proto::DownloadAddBatchResult> on_download_addBatch(const proto::DownloadAddBatchParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadAddBatchResult>("download.addBatch"); return golden<proto::DownloadAddBatchResult>("download.addBatch");
} }
proto::Result<proto::BulkTaskResult> on_download_cancel(const proto::DownloadCancelParams& params) override { proto::HandlerResult<proto::BulkTaskResult> on_download_cancel(const proto::DownloadCancelParams& params) override {
(void)params; (void)params;
return golden<proto::BulkTaskResult>("download.cancel"); return golden<proto::BulkTaskResult>("download.cancel");
} }
proto::Result<proto::TaskDetail> on_download_get(const proto::DownloadGetParams& params) override { proto::HandlerResult<proto::TaskDetail> on_download_get(const proto::DownloadGetParams& params) override {
(void)params; (void)params;
return golden<proto::TaskDetail>("download.get"); return golden<proto::TaskDetail>("download.get");
} }
proto::Result<proto::DownloadListResult> on_download_list(const proto::DownloadListParams& params) override { proto::HandlerResult<proto::DownloadListResult> on_download_list(const proto::DownloadListParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadListResult>("download.list"); return golden<proto::DownloadListResult>("download.list");
} }
proto::Result<proto::BulkTaskResult> on_download_pause(const proto::DownloadPauseParams& params) override { proto::HandlerResult<proto::BulkTaskResult> on_download_pause(const proto::DownloadPauseParams& params) override {
(void)params; (void)params;
return golden<proto::BulkTaskResult>("download.pause"); return golden<proto::BulkTaskResult>("download.pause");
} }
proto::Result<proto::DownloadProbeResult> on_download_probe(const proto::DownloadProbeParams& params) override { proto::HandlerResult<proto::DownloadProbeResult> on_download_probe(const proto::DownloadProbeParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadProbeResult>("download.probe"); return golden<proto::DownloadProbeResult>("download.probe");
} }
proto::Result<proto::DownloadProvideAuthResult> on_download_provideAuth(const proto::DownloadProvideAuthParams& params) override { proto::HandlerResult<proto::DownloadProvideAuthResult> on_download_provideAuth(const proto::DownloadProvideAuthParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadProvideAuthResult>("download.provideAuth"); return golden<proto::DownloadProvideAuthResult>("download.provideAuth");
} }
proto::Result<proto::DownloadRefreshUrlResult> on_download_refreshUrl(const proto::DownloadRefreshUrlParams& params) override { proto::HandlerResult<proto::DownloadRefreshUrlResult> on_download_refreshUrl(const proto::DownloadRefreshUrlParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadRefreshUrlResult>("download.refreshUrl"); return golden<proto::DownloadRefreshUrlResult>("download.refreshUrl");
} }
proto::Result<proto::DownloadRemoveResult> on_download_remove(const proto::DownloadRemoveParams& params) override { proto::HandlerResult<proto::DownloadRemoveResult> on_download_remove(const proto::DownloadRemoveParams& params) override {
(void)params; (void)params;
return golden<proto::DownloadRemoveResult>("download.remove"); return golden<proto::DownloadRemoveResult>("download.remove");
} }
proto::Result<proto::BulkTaskResult> on_download_resume(const proto::DownloadResumeParams& params) override { proto::HandlerResult<proto::BulkTaskResult> on_download_resume(const proto::DownloadResumeParams& params) override {
(void)params; (void)params;
return golden<proto::BulkTaskResult>("download.resume"); return golden<proto::BulkTaskResult>("download.resume");
} }
proto::Result<proto::BulkTaskResult> on_download_start(const proto::DownloadStartParams& params) override { proto::HandlerResult<proto::BulkTaskResult> on_download_start(const proto::DownloadStartParams& params) override {
(void)params; (void)params;
return golden<proto::BulkTaskResult>("download.start"); return golden<proto::BulkTaskResult>("download.start");
} }
proto::Result<proto::TaskSummary> on_download_update(const proto::DownloadUpdateParams& params) override { proto::HandlerResult<proto::TaskSummary> on_download_update(const proto::DownloadUpdateParams& params) override {
(void)params; (void)params;
return golden<proto::TaskSummary>("download.update"); return golden<proto::TaskSummary>("download.update");
} }
proto::Result<proto::GrabberHarvestResult> on_grabber_harvest(const proto::GrabberHarvestParams& params) override { proto::HandlerResult<proto::GrabberHarvestResult> on_grabber_harvest(const proto::GrabberHarvestParams& params) override {
(void)params; (void)params;
return golden<proto::GrabberHarvestResult>("grabber.harvest"); return golden<proto::GrabberHarvestResult>("grabber.harvest");
} }
proto::Result<proto::GrabberStartResult> on_grabber_start(const proto::GrabberStartParams& params) override { proto::HandlerResult<proto::GrabberStartResult> on_grabber_start(const proto::GrabberStartParams& params) override {
(void)params; (void)params;
return golden<proto::GrabberStartResult>("grabber.start"); return golden<proto::GrabberStartResult>("grabber.start");
} }
proto::Result<proto::GrabberStatusResult> on_grabber_status(const proto::GrabberStatusParams& params) override { proto::HandlerResult<proto::GrabberStatusResult> on_grabber_status(const proto::GrabberStatusParams& params) override {
(void)params; (void)params;
return golden<proto::GrabberStatusResult>("grabber.status"); return golden<proto::GrabberStatusResult>("grabber.status");
} }
proto::Result<proto::Limiter> on_limiter_get(const proto::LimiterGetParams& params) override { proto::HandlerResult<proto::Limiter> on_limiter_get(const proto::LimiterGetParams& params) override {
(void)params; (void)params;
return golden<proto::Limiter>("limiter.get"); return golden<proto::Limiter>("limiter.get");
} }
proto::Result<proto::Limiter> on_limiter_set(const proto::Limiter& params) override { proto::HandlerResult<proto::Limiter> on_limiter_set(const proto::Limiter& params) override {
(void)params; (void)params;
return golden<proto::Limiter>("limiter.set"); return golden<proto::Limiter>("limiter.set");
} }
proto::Result<proto::MediaAddVariantResult> on_media_addVariant(const proto::MediaAddVariantParams& params) override { proto::HandlerResult<proto::MediaAddVariantResult> on_media_addVariant(const proto::MediaAddVariantParams& params) override {
(void)params; (void)params;
return golden<proto::MediaAddVariantResult>("media.addVariant"); return golden<proto::MediaAddVariantResult>("media.addVariant");
} }
proto::Result<proto::MediaListVariantsResult> on_media_listVariants(const proto::MediaListVariantsParams& params) override { proto::HandlerResult<proto::MediaListVariantsResult> on_media_listVariants(const proto::MediaListVariantsParams& params) override {
(void)params; (void)params;
return golden<proto::MediaListVariantsResult>("media.listVariants"); return golden<proto::MediaListVariantsResult>("media.listVariants");
} }
proto::Result<proto::QueueListResult> on_queue_list(const proto::QueueListParams& params) override { proto::HandlerResult<proto::QueueListResult> on_queue_list(const proto::QueueListParams& params) override {
(void)params; (void)params;
return golden<proto::QueueListResult>("queue.list"); return golden<proto::QueueListResult>("queue.list");
} }
proto::Result<proto::QueueReorderResult> on_queue_reorder(const proto::QueueReorderParams& params) override { proto::HandlerResult<proto::QueueReorderResult> on_queue_reorder(const proto::QueueReorderParams& params) override {
(void)params; (void)params;
return golden<proto::QueueReorderResult>("queue.reorder"); return golden<proto::QueueReorderResult>("queue.reorder");
} }
proto::Result<proto::QueueStartResult> on_queue_start(const proto::QueueStartParams& params) override { proto::HandlerResult<proto::QueueStartResult> on_queue_start(const proto::QueueStartParams& params) override {
(void)params; (void)params;
return golden<proto::QueueStartResult>("queue.start"); return golden<proto::QueueStartResult>("queue.start");
} }
proto::Result<proto::QueueStopResult> on_queue_stop(const proto::QueueStopParams& params) override { proto::HandlerResult<proto::QueueStopResult> on_queue_stop(const proto::QueueStopParams& params) override {
(void)params; (void)params;
return golden<proto::QueueStopResult>("queue.stop"); return golden<proto::QueueStopResult>("queue.stop");
} }
proto::Result<proto::QueueUpsertResult> on_queue_upsert(const proto::QueueUpsertParams& params) override { proto::HandlerResult<proto::QueueUpsertResult> on_queue_upsert(const proto::QueueUpsertParams& params) override {
(void)params; (void)params;
return golden<proto::QueueUpsertResult>("queue.upsert"); return golden<proto::QueueUpsertResult>("queue.upsert");
} }
proto::Result<proto::RulesListResult> on_rules_list(const proto::RulesListParams& params) override { proto::HandlerResult<proto::RulesListResult> on_rules_list(const proto::RulesListParams& params) override {
(void)params; (void)params;
return golden<proto::RulesListResult>("rules.list"); return golden<proto::RulesListResult>("rules.list");
} }
proto::Result<proto::RulesUpsertResult> on_rules_upsert(const proto::RulesUpsertParams& params) override { proto::HandlerResult<proto::RulesUpsertResult> on_rules_upsert(const proto::RulesUpsertParams& params) override {
(void)params; (void)params;
return golden<proto::RulesUpsertResult>("rules.upsert"); return golden<proto::RulesUpsertResult>("rules.upsert");
} }
proto::Result<proto::ScheduleGetResult> on_schedule_get(const proto::ScheduleGetParams& params) override { proto::HandlerResult<proto::ScheduleGetResult> on_schedule_get(const proto::ScheduleGetParams& params) override {
(void)params; (void)params;
return golden<proto::ScheduleGetResult>("schedule.get"); return golden<proto::ScheduleGetResult>("schedule.get");
} }
proto::Result<proto::ScheduleSetResult> on_schedule_set(const proto::ScheduleSetParams& params) override { proto::HandlerResult<proto::ScheduleSetResult> on_schedule_set(const proto::ScheduleSetParams& params) override {
(void)params; (void)params;
return golden<proto::ScheduleSetResult>("schedule.set"); return golden<proto::ScheduleSetResult>("schedule.set");
} }
proto::Result<proto::SessionHelloResult> on_session_hello(const proto::SessionHelloParams& params) override { proto::HandlerResult<proto::SessionHelloResult> on_session_hello(const proto::SessionHelloParams& params) override {
(void)params; (void)params;
return golden<proto::SessionHelloResult>("session.hello"); return golden<proto::SessionHelloResult>("session.hello");
} }
proto::Result<proto::SessionPairResult> on_session_pair(const proto::SessionPairParams& params) override { proto::HandlerResult<proto::SessionPairResult> on_session_pair(const proto::SessionPairParams& params) override {
(void)params; (void)params;
return golden<proto::SessionPairResult>("session.pair"); return golden<proto::SessionPairResult>("session.pair");
} }
proto::Result<proto::SessionSubscribeResult> on_session_subscribe(const proto::SessionSubscribeParams& params) override { proto::HandlerResult<proto::SessionSubscribeResult> on_session_subscribe(const proto::SessionSubscribeParams& params) override {
(void)params; (void)params;
return golden<proto::SessionSubscribeResult>("session.subscribe"); return golden<proto::SessionSubscribeResult>("session.subscribe");
} }
proto::Result<proto::SettingsGetResult> on_settings_get(const proto::SettingsGetParams& params) override { proto::HandlerResult<proto::SettingsGetResult> on_settings_get(const proto::SettingsGetParams& params) override {
(void)params; (void)params;
return golden<proto::SettingsGetResult>("settings.get"); return golden<proto::SettingsGetResult>("settings.get");
} }
proto::Result<proto::SettingsSetResult> on_settings_set(const proto::SettingsSetParams& params) override { proto::HandlerResult<proto::SettingsSetResult> on_settings_set(const proto::SettingsSetParams& params) override {
(void)params; (void)params;
return golden<proto::SettingsSetResult>("settings.set"); return golden<proto::SettingsSetResult>("settings.set");
} }
private: private:
// Every fixture-backed handler only ever succeeds. A missing or unparseable
// fixture is a bug in the suite, not a contract outcome, so it surfaces as
// InternalError rather than being dressed up as a real error code.
template <class T> template <class T>
proto::Result<T> golden(const std::string& method) { proto::HandlerResult<T> golden(const std::string& method) {
const nlohmann::json* value = results_(method); const nlohmann::json* value = results_(method);
if (value == nullptr) if (value == nullptr)
return std::unexpected(proto::ParseError{method, "no fixture for this method"}); return std::unexpected(proto::HandlerError{
return proto::parse<T>(*value, method); proto::ErrorCode::InternalError, "no fixture for method " + method});
auto parsed = proto::parse<T>(*value, method);
if (!parsed)
return std::unexpected(proto::HandlerError{
proto::ErrorCode::InternalError,
"fixture for " + method + " failed to parse: " + parsed.error().message});
return std::move(*parsed);
} }
std::function<const nlohmann::json*(const std::string&)> results_; std::function<const nlohmann::json*(const std::string&)> results_;