pub mod admin; pub mod messages; pub mod peers; pub mod pubkey; pub mod routes; pub mod topics; use crate::error::{AppError, AppResult}; use reqwest::{Client, Response}; use serde::de::DeserializeOwned; use std::time::Duration; /// Thin REST client for the mycelium daemon's HTTP API. /// /// The base URL is set after the sidecar reports ready; clients are cheap /// to clone (the inner `reqwest::Client` keeps a shared connection pool). #[derive(Debug, Clone)] pub struct MyceliumClient { base: String, http: Client, } impl MyceliumClient { pub fn new(base: impl Into) -> Self { // Mycelium's HTTP server seems to drop idle keep-alive connections // around the 10s mark; reusing a pooled stale connection surfaces // as a generic "error sending request" once `start_daemon` // returned. Open a fresh TCP connection per request — overhead is // negligible on loopback and immune to server-side closes. let http = Client::builder() .pool_max_idle_per_host(0) .timeout(Duration::from_secs(10)) .connect_timeout(Duration::from_secs(3)) .build() .expect("reqwest client build"); Self { base: base.into(), http, } } pub fn base_url(&self) -> &str { &self.base } pub(crate) fn url(&self, path: &str) -> String { format!("{}/api/v1{}", self.base, path) } pub(crate) async fn parse(resp: Response) -> AppResult { let status = resp.status(); if status.is_success() { resp.json::().await.map_err(AppError::from) } else { let body = resp.text().await.unwrap_or_default(); Err(AppError::DaemonStatus { status: status.as_u16(), body, }) } } pub(crate) async fn check_status(resp: Response) -> AppResult<()> { let status = resp.status(); if status.is_success() { Ok(()) } else { let body = resp.text().await.unwrap_or_default(); Err(AppError::DaemonStatus { status: status.as_u16(), body, }) } } pub(crate) fn http(&self) -> &Client { &self.http } }