radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit b452c0cf6703b6d0f3a4f333fff125a9c343f41d
parent f1167c031bf5aee848d90ddb73bb4605423d839c
Author: triesap <triesap@radroots.dev>
Date:   Sat,  3 Jan 2026 18:28:28 +0000

jsonrpc: replace relay responses with typed structs

- Add relays module with Serialize response types
- Return RelayAdded/Removed/Connect response structs instead of json! values
- Switch relays.list to return Vec<String> directly
- Default relays.status params and emit typed RelayStatusRow with optional nip11

Diffstat:
Msrc/api/jsonrpc/methods/relays/add.rs | 4++--
Msrc/api/jsonrpc/methods/relays/connect.rs | 14+++++++-------
Msrc/api/jsonrpc/methods/relays/list.rs | 6++----
Msrc/api/jsonrpc/methods/relays/remove.rs | 4++--
Msrc/api/jsonrpc/methods/relays/status.rs | 37++++++++++++++++++++++---------------
Msrc/api/jsonrpc/methods/system.rs | 17++++++++++++-----
Msrc/api/jsonrpc/mod.rs | 1+
Asrc/api/jsonrpc/relays.rs | 34++++++++++++++++++++++++++++++++++
8 files changed, 82 insertions(+), 35 deletions(-)

diff --git a/src/api/jsonrpc/methods/relays/add.rs b/src/api/jsonrpc/methods/relays/add.rs @@ -2,8 +2,8 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use radroots_nostr::prelude::radroots_nostr_add_relay; use serde::Deserialize; -use serde_json::{Value as JsonValue, json}; +use crate::api::jsonrpc::relays::RelayAddedResponse; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; #[derive(Debug, Deserialize)] @@ -22,7 +22,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res .await .map_err(|e| RpcError::AddRelay(url.clone(), e.to_string()))?; - Ok::<JsonValue, RpcError>(json!({ "added": url })) + Ok::<RelayAddedResponse, RpcError>(RelayAddedResponse { added: url }) })?; Ok(()) } diff --git a/src/api/jsonrpc/methods/relays/connect.rs b/src/api/jsonrpc/methods/relays/connect.rs @@ -1,7 +1,7 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; -use serde_json::{Value as JsonValue, json}; +use crate::api::jsonrpc::relays::RelayConnectResponse; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_nostr::prelude::{radroots_nostr_connect, RadrootsNostrRelayStatus}; @@ -32,12 +32,12 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res tokio::spawn(async move { radroots_nostr_connect(&client).await }); } - Ok::<JsonValue, RpcError>(json!({ - "connected": connected, - "connecting": connecting, - "disconnected": disconnected, - "spawned_connect": need_connect - })) + Ok::<RelayConnectResponse, RpcError>(RelayConnectResponse { + connected, + connecting, + disconnected, + spawned_connect: need_connect, + }) })?; Ok(()) } diff --git a/src/api/jsonrpc/methods/relays/list.rs b/src/api/jsonrpc/methods/relays/list.rs @@ -1,6 +1,5 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; -use serde_json::{Value as JsonValue, json}; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; @@ -8,9 +7,8 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res registry.track("relays.list"); m.register_async_method("relays.list", |_p, ctx, _| async move { let relays = ctx.state.client.relays().await; - Ok::<JsonValue, RpcError>(json!( - relays.keys().map(|u| u.to_string()).collect::<Vec<_>>() - )) + let out = relays.keys().map(|u| u.to_string()).collect::<Vec<_>>(); + Ok::<Vec<String>, RpcError>(out) })?; Ok(()) } diff --git a/src/api/jsonrpc/methods/relays/remove.rs b/src/api/jsonrpc/methods/relays/remove.rs @@ -2,8 +2,8 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use radroots_nostr::prelude::radroots_nostr_remove_relay; use serde::Deserialize; -use serde_json::{Value as JsonValue, json}; +use crate::api::jsonrpc::relays::RelayRemovedResponse; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; #[derive(Debug, Deserialize)] @@ -22,7 +22,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res .await .map_err(|e| RpcError::Other(format!("failed to remove relay {url}: {e}")))?; - Ok::<JsonValue, RpcError>(json!({ "removed": url })) + Ok::<RelayRemovedResponse, RpcError>(RelayRemovedResponse { removed: url }) })?; Ok(()) } diff --git a/src/api/jsonrpc/methods/relays/status.rs b/src/api/jsonrpc/methods/relays/status.rs @@ -1,12 +1,12 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; use serde::Deserialize; -use serde_json::{Map as JsonMap, Value as JsonValue, json}; +use crate::api::jsonrpc::relays::RelayStatusRow; use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError}; use radroots_nostr::prelude::fetch_nip11; -#[derive(Debug, Deserialize)] +#[derive(Debug, Default, Deserialize)] struct StatusParams { #[serde(default)] include_nip11: bool, @@ -16,8 +16,9 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res registry.track("relays.status"); m.register_async_method("relays.status", |params, ctx, _| async move { let StatusParams { include_nip11 } = params - .parse() - .map_err(|e| RpcError::InvalidParams(e.to_string()))?; + .parse::<Option<StatusParams>>() + .map_err(|e| RpcError::InvalidParams(e.to_string()))? + .unwrap_or_default(); let relays = ctx.state.client.relays().await; let mut out = Vec::with_capacity(relays.len()); @@ -27,31 +28,37 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res let status_str = format!("{}", relay.status()); let parsed = reqwest::Url::parse(&url_str).ok(); - let mut row = JsonMap::new(); - row.insert("url".into(), json!(url_str)); - row.insert("status".into(), json!(status_str)); + let mut row = RelayStatusRow { + url: url_str.clone(), + status: status_str, + scheme: None, + host: None, + onion: None, + port: None, + nip11: None, + }; if let Some(u) = &parsed { - row.insert("scheme".into(), json!(u.scheme())); + row.scheme = Some(u.scheme().to_string()); if let Some(h) = u.host_str() { - row.insert("host".into(), json!(h)); - row.insert("onion".into(), json!(h.ends_with(".onion"))); + row.host = Some(h.to_string()); + row.onion = Some(h.ends_with(".onion")); } if let Some(p) = u.port() { - row.insert("port".into(), json!(p)); + row.port = Some(p); } } if include_nip11 { - if let Some(doc) = fetch_nip11(row["url"].as_str().unwrap()).await { - row.insert("nip11".into(), json!(doc)); + if let Some(doc) = fetch_nip11(&row.url).await { + row.nip11 = Some(doc); } } - out.push(JsonValue::Object(row)); + out.push(row); } - Ok::<JsonValue, RpcError>(json!(out)) + Ok::<Vec<RelayStatusRow>, RpcError>(out) })?; Ok(()) } diff --git a/src/api/jsonrpc/methods/system.rs b/src/api/jsonrpc/methods/system.rs @@ -1,9 +1,16 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; -use serde_json::json; +use serde::Serialize; use crate::api::jsonrpc::{MethodRegistry, RpcContext}; +#[derive(Clone, Debug, Serialize)] +struct SystemInfoResponse { + version: Option<serde_json::Value>, + build: Option<serde_json::Value>, + uptime_secs: u64, +} + pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<RpcContext>> { let mut m = RpcModule::new(ctx); @@ -13,10 +20,10 @@ pub fn module(ctx: RpcContext, registry: MethodRegistry) -> Result<RpcModule<Rpc registry.track("system.get_info"); m.register_method("system.get_info", |_p, ctx, _| { let uptime = ctx.state.started.elapsed().as_secs(); - json!({ - "version": ctx.state.info.get("version"), - "build": ctx.state.info.get("build"), - "uptime_secs": uptime, + Ok::<SystemInfoResponse, crate::api::jsonrpc::RpcError>(SystemInfoResponse { + version: ctx.state.info.get("version").cloned(), + build: ctx.state.info.get("build").cloned(), + uptime_secs: uptime, }) })?; diff --git a/src/api/jsonrpc/mod.rs b/src/api/jsonrpc/mod.rs @@ -12,6 +12,7 @@ mod context; mod error; mod nostr; mod params; +mod relays; mod registry; mod server; diff --git a/src/api/jsonrpc/relays.rs b/src/api/jsonrpc/relays.rs @@ -0,0 +1,34 @@ +#![forbid(unsafe_code)] + +use serde::Serialize; + +use radroots_events::relay_document::RadrootsRelayDocument; + +#[derive(Clone, Debug, Serialize)] +pub(crate) struct RelayAddedResponse { + pub added: String, +} + +#[derive(Clone, Debug, Serialize)] +pub(crate) struct RelayRemovedResponse { + pub removed: String, +} + +#[derive(Clone, Debug, Serialize)] +pub(crate) struct RelayConnectResponse { + pub connected: usize, + pub connecting: usize, + pub disconnected: usize, + pub spawned_connect: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub(crate) struct RelayStatusRow { + pub url: String, + pub status: String, + pub scheme: Option<String>, + pub host: Option<String>, + pub onion: Option<bool>, + pub port: Option<u16>, + pub nip11: Option<RadrootsRelayDocument>, +}