Skip to main content

synapse_client/
lib.rs

1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum SynapseClientError {
6    #[error("Connection error: {0}")]
7    Connection(String),
8    #[error("Request error: {0}")]
9    Request(String),
10    #[error("Query error: {query_name}: {message}")]
11    Query { query_name: String, message: String },
12}
13
14pub type Result<T> = std::result::Result<T, SynapseClientError>;
15
16/// Synapse client — connects to a running Synapse runtime over HTTP.
17///
18/// ```rust,no_run
19/// use synapse_client::Client;
20///
21/// #[tokio::main]
22/// async fn main() {
23///     let client = Client::new("http://localhost:8080");
24///     client.emit("save", serde_json::json!({"content": "hello"})).await.unwrap();
25///     let results = client.query("GetAll", serde_json::json!({})).await.unwrap();
26/// }
27/// ```
28#[derive(Debug, Clone)]
29pub struct Client {
30    base_url: String,
31    http: reqwest::Client,
32}
33
34impl Client {
35    pub fn new(base_url: &str) -> Self {
36        Self {
37            base_url: base_url.trim_end_matches('/').to_string(),
38            http: reqwest::Client::new(),
39        }
40    }
41
42    pub fn with_timeout(base_url: &str, timeout: std::time::Duration) -> Self {
43        Self {
44            base_url: base_url.trim_end_matches('/').to_string(),
45            http: reqwest::Client::builder()
46                .timeout(timeout)
47                .build()
48                .unwrap_or_default(),
49        }
50    }
51
52    /// Emit an event to trigger a handler.
53    pub async fn emit(&self, event: &str, payload: serde_json::Value) -> Result<serde_json::Value> {
54        let url = format!("{}/emit", self.base_url);
55        let body = serde_json::json!({
56            "event": event,
57            "payload": payload,
58        });
59
60        let resp = self
61            .http
62            .post(&url)
63            .json(&body)
64            .send()
65            .await
66            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
67
68        if !resp.status().is_success() {
69            let msg = resp.text().await.unwrap_or_default();
70            return Err(SynapseClientError::Request(msg));
71        }
72
73        resp.json()
74            .await
75            .map_err(|e| SynapseClientError::Request(e.to_string()))
76    }
77
78    /// Execute a named query.
79    pub async fn query(
80        &self,
81        query_name: &str,
82        params: serde_json::Value,
83    ) -> Result<serde_json::Value> {
84        let url = format!("{}/query", self.base_url);
85        let body = serde_json::json!({
86            "query": query_name,
87            "params": params,
88        });
89
90        let resp = self
91            .http
92            .post(&url)
93            .json(&body)
94            .send()
95            .await
96            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
97
98        if !resp.status().is_success() {
99            let msg = resp.text().await.unwrap_or_default();
100            return Err(SynapseClientError::Query {
101                query_name: query_name.into(),
102                message: msg,
103            });
104        }
105
106        resp.json()
107            .await
108            .map_err(|e| SynapseClientError::Request(e.to_string()))
109    }
110
111    /// Health check.
112    pub async fn health(&self) -> Result<HealthResponse> {
113        let url = format!("{}/health", self.base_url);
114        let resp = self
115            .http
116            .get(&url)
117            .send()
118            .await
119            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
120
121        resp.json()
122            .await
123            .map_err(|e| SynapseClientError::Request(e.to_string()))
124    }
125
126    /// Get runtime status.
127    pub async fn status(&self) -> Result<StatusResponse> {
128        let url = format!("{}/status", self.base_url);
129        let resp = self
130            .http
131            .get(&url)
132            .send()
133            .await
134            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
135
136        resp.json()
137            .await
138            .map_err(|e| SynapseClientError::Request(e.to_string()))
139    }
140
141    /// Trigger a hot reload of the runtime.
142    pub async fn reload(&self) -> Result<serde_json::Value> {
143        let url = format!("{}/reload", self.base_url);
144        let resp = self
145            .http
146            .post(&url)
147            .send()
148            .await
149            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
150
151        if !resp.status().is_success() {
152            let msg = resp.text().await.unwrap_or_default();
153            return Err(SynapseClientError::Request(msg));
154        }
155
156        resp.json()
157            .await
158            .map_err(|e| SynapseClientError::Request(e.to_string()))
159    }
160
161    /// Clear all records from all configured backends.
162    pub async fn clear(&self) -> Result<serde_json::Value> {
163        let url = format!("{}/clear", self.base_url);
164        let resp = self
165            .http
166            .post(&url)
167            .send()
168            .await
169            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
170
171        if !resp.status().is_success() {
172            let msg = resp.text().await.unwrap_or_default();
173            return Err(SynapseClientError::Request(msg));
174        }
175
176        resp.json()
177            .await
178            .map_err(|e| SynapseClientError::Request(e.to_string()))
179    }
180
181    /// Inspect all configured backends: list tables/collections and their records.
182    pub async fn inspect(&self) -> Result<serde_json::Value> {
183        let url = format!("{}/inspect", self.base_url);
184        let resp = self
185            .http
186            .get(&url)
187            .send()
188            .await
189            .map_err(|e| SynapseClientError::Connection(e.to_string()))?;
190
191        if !resp.status().is_success() {
192            let msg = resp.text().await.unwrap_or_default();
193            return Err(SynapseClientError::Request(msg));
194        }
195
196        resp.json()
197            .await
198            .map_err(|e| SynapseClientError::Request(e.to_string()))
199    }
200
201    /// Simple connectivity check.
202    pub async fn ping(&self) -> bool {
203        self.health().await.is_ok()
204    }
205}
206
207#[derive(Debug, Serialize, Deserialize)]
208pub struct HealthResponse {
209    pub status: String,
210    pub uptime_secs: i64,
211}
212
213#[derive(Debug, Serialize, Deserialize)]
214pub struct StatusResponse {
215    pub status: String,
216    pub handlers: Vec<String>,
217    pub queries: Vec<String>,
218    pub memories: Vec<String>,
219    pub uptime_secs: i64,
220}