generated from eblume/project-template
Some checks failed
Build / validate (pull_request) Failing after 3s
Slice 6 (tech-spec §3, §6, §10). First async component — the per-device daemon in local mode. - `LockGuard`: exclusive advisory flock on a sidecar `<db>.lock`; a second acquire fails and releases on drop (the §3.1 lock handoff). - JSON-RPC (line-delimited): `rpc::dispatch` maps node/task/next/links/log methods onto the heph-core Store; `Daemon::serve` accepts unix-socket connections and runs dispatch on tokio's blocking pool behind an Arc<Mutex<LocalStore>> (DB never touches an async worker). - Synchronous `Client` for surfaces/CLI; `hephd` binary (clap) opens the store under lock and serves the default socket. - heph-core model/ranking types are now serde-(de)serializable; added node.tombstone + Store::tombstone_node. Tests: 2 lock unit tests + 5 real-socket e2e (round-trip with clock injection, next, error paths, recurring roll-forward over RPC, 8-client concurrency). 60 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
2 KiB
Rust
61 lines
2 KiB
Rust
//! A minimal **synchronous** JSON-RPC client over the unix socket.
|
|
//!
|
|
//! Used by the `heph` CLI and by tests. Surfaces never touch SQLite directly
|
|
//! (tech-spec §3) — they go through the daemon socket, which this wraps.
|
|
|
|
use std::io::{BufRead, BufReader, Write};
|
|
use std::os::unix::net::UnixStream;
|
|
use std::path::Path;
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
use serde_json::{json, Value};
|
|
|
|
use crate::rpc::Response;
|
|
|
|
/// A connected client. One request/response per [`call`](Client::call).
|
|
pub struct Client {
|
|
reader: BufReader<UnixStream>,
|
|
writer: UnixStream,
|
|
next_id: u64,
|
|
}
|
|
|
|
impl Client {
|
|
/// Connect to a daemon listening at `socket_path`.
|
|
pub fn connect(socket_path: &Path) -> Result<Client> {
|
|
let stream = UnixStream::connect(socket_path)
|
|
.with_context(|| format!("connecting to hephd at {}", socket_path.display()))?;
|
|
let reader = BufReader::new(stream.try_clone()?);
|
|
Ok(Client {
|
|
reader,
|
|
writer: stream,
|
|
next_id: 1,
|
|
})
|
|
}
|
|
|
|
/// Call `method` with `params`, returning the `result` value (or an error
|
|
/// carrying the RPC error's code and message).
|
|
pub fn call(&mut self, method: &str, params: Value) -> Result<Value> {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
|
|
let mut line = serde_json::to_string(&json!({
|
|
"id": id,
|
|
"method": method,
|
|
"params": params,
|
|
}))?;
|
|
line.push('\n');
|
|
self.writer.write_all(line.as_bytes())?;
|
|
self.writer.flush()?;
|
|
|
|
let mut response_line = String::new();
|
|
let read = self.reader.read_line(&mut response_line)?;
|
|
if read == 0 {
|
|
bail!("hephd closed the connection");
|
|
}
|
|
let response: Response = serde_json::from_str(&response_line)?;
|
|
if let Some(err) = response.error {
|
|
bail!("rpc error {}: {}", err.code, err.message);
|
|
}
|
|
Ok(response.result.unwrap_or(Value::Null))
|
|
}
|
|
}
|