1use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicU64, Ordering};
11use tokio::sync::{mpsc, oneshot};
12
13pub const JSONRPC_VERSION: &str = "2.0";
17
18pub const OUTBOUND_ID_PREFIX: &str = "zc-out-";
21
22pub mod field {
26 pub const JSONRPC: &str = "jsonrpc";
27 pub const METHOD: &str = "method";
28 pub const PARAMS: &str = "params";
29 pub const ID: &str = "id";
30 pub const RESULT: &str = "result";
31 pub const ERROR: &str = "error";
32}
33
34#[derive(Debug, Serialize, Deserialize)]
37pub struct JsonRpcRequest {
38 pub jsonrpc: String,
39 pub method: String,
40 #[serde(default)]
41 pub params: Value,
42 pub id: Option<Value>,
43}
44
45impl JsonRpcRequest {
46 pub fn new(method: &str, params: Value, id: Value) -> Self {
48 Self {
49 jsonrpc: JSONRPC_VERSION.to_string(),
50 method: method.to_string(),
51 params,
52 id: Some(id),
53 }
54 }
55}
56
57#[derive(Debug, Serialize)]
58pub struct JsonRpcResponse {
59 pub jsonrpc: &'static str,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub result: Option<Value>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub error: Option<JsonRpcError>,
64 pub id: Value,
65}
66
67#[derive(Debug, Serialize)]
68pub struct JsonRpcNotification {
69 pub jsonrpc: &'static str,
70 pub method: &'static str,
71 pub params: Value,
72}
73
74impl JsonRpcNotification {
75 pub fn new(method: &'static str, params: Value) -> Self {
76 Self {
77 jsonrpc: JSONRPC_VERSION,
78 method,
79 params,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct JsonRpcError {
86 pub code: i32,
87 pub message: String,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub data: Option<Value>,
90}
91
92pub mod error_codes {
95 pub const PARSE_ERROR: i32 = -32700;
97 pub const INVALID_REQUEST: i32 = -32600;
98 pub const METHOD_NOT_FOUND: i32 = -32601;
99 pub const INVALID_PARAMS: i32 = -32602;
100 pub const INTERNAL_ERROR: i32 = -32603;
101
102 pub const SESSION_NOT_FOUND: i32 = -32000;
104 pub const SESSION_LIMIT_REACHED: i32 = -32001;
105 pub const SESSION_BUSY: i32 = -32002;
106 pub const SESSION_NOT_OWNED: i32 = -32003;
107 pub const AUTH_REQUIRED: i32 = -32010;
108 pub const VERSION_MISMATCH: i32 = -32011;
109
110 pub const FS_NOT_FOUND: i32 = 4001;
112 pub const FS_PERMISSION_DENIED: i32 = 4002;
113 pub const FS_INVALID_PATH: i32 = 4003;
114
115 pub const FS_NOT_FOUND_STR: &str = "fs.not_found";
117 pub const FS_PERMISSION_DENIED_STR: &str = "fs.permission_denied";
118 pub const FS_INVALID_PATH_STR: &str = "fs.invalid_path";
119}
120
121pub const ACP_PROTOCOL_VERSION: u64 = 1;
122
123type PendingResponder = oneshot::Sender<std::result::Result<Value, JsonRpcError>>;
126
127#[derive(Debug)]
134pub struct RpcOutbound {
135 writer_tx: mpsc::Sender<String>,
136 pending: std::sync::Mutex<HashMap<String, PendingResponder>>,
137 next_id: AtomicU64,
138}
139
140struct PendingRequestGuard<'a> {
141 pending: &'a std::sync::Mutex<HashMap<String, PendingResponder>>,
142 id: String,
143}
144
145impl Drop for PendingRequestGuard<'_> {
146 fn drop(&mut self) {
147 self.pending
148 .lock()
149 .unwrap_or_else(|e| e.into_inner())
150 .remove(&self.id);
151 }
152}
153
154impl RpcOutbound {
155 pub fn new(writer_tx: mpsc::Sender<String>) -> Self {
156 Self {
157 writer_tx,
158 pending: std::sync::Mutex::new(HashMap::new()),
159 next_id: AtomicU64::new(0),
160 }
161 }
162
163 pub async fn send_raw(&self, json: String) -> bool {
165 self.writer_tx.send(json).await.is_ok()
166 }
167
168 pub async fn closed(&self) {
172 self.writer_tx.closed().await;
173 }
174
175 pub async fn notify(&self, method: &'static str, params: Value) {
177 let n = JsonRpcNotification::new(method, params);
178 if let Ok(s) = serde_json::to_string(&n) {
179 let _ = self.writer_tx.send(s).await;
180 }
181 }
182
183 pub async fn request(
185 &self,
186 method: &str,
187 params: Value,
188 ) -> std::result::Result<Value, JsonRpcError> {
189 let n = self.next_id.fetch_add(1, Ordering::Relaxed);
190 let id = format!("{OUTBOUND_ID_PREFIX}{n}");
191 let (tx, rx) = oneshot::channel();
192 {
193 let mut pending = self.pending.lock().unwrap_or_else(|e| e.into_inner());
194 pending.insert(id.clone(), tx);
195 }
196 let _pending_guard = PendingRequestGuard {
197 pending: &self.pending,
198 id: id.clone(),
199 };
200 let req = JsonRpcRequest::new(method, params, Value::String(id));
201 let body = match serde_json::to_string(&req) {
202 Ok(s) => s,
203 Err(e) => {
204 return Err(JsonRpcError {
205 code: error_codes::INTERNAL_ERROR,
206 message: format!("Failed to encode request: {e}"),
207 data: None,
208 });
209 }
210 };
211 if self.writer_tx.send(body).await.is_err() {
212 return Err(JsonRpcError {
213 code: error_codes::INTERNAL_ERROR,
214 message: "Writer task closed".to_string(),
215 data: None,
216 });
217 }
218 rx.await.unwrap_or_else(|_| {
219 Err(JsonRpcError {
220 code: error_codes::INTERNAL_ERROR,
221 message: "Outbound RPC dropped".to_string(),
222 data: None,
223 })
224 })
225 }
226
227 pub fn dispatch_response(
229 &self,
230 id_str: &str,
231 result: Option<Value>,
232 error: Option<JsonRpcError>,
233 ) {
234 let responder = self
235 .pending
236 .lock()
237 .unwrap_or_else(|e| e.into_inner())
238 .remove(id_str);
239 if let Some(tx) = responder {
240 let payload = if let Some(err) = error {
241 Err(err)
242 } else {
243 Ok(result.unwrap_or(Value::Null))
244 };
245 let _ = tx.send(payload);
246 }
247 }
248
249 pub fn pending_count(&self) -> usize {
251 self.pending.lock().unwrap_or_else(|e| e.into_inner()).len()
252 }
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct LocaleOption {
260 pub code: String,
261 pub label: String,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct LocalesListResponse {
267 pub locales: Vec<LocaleOption>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct LocalesFetchRequest {
275 pub locale: String,
276 #[serde(default)]
277 pub catalog: Vec<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct FetchedCatalog {
285 pub name: String,
286 pub filename: String,
288 pub content: String,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct LocalesFetchResponse {
295 pub locale: String,
296 pub catalogs: Vec<FetchedCatalog>,
297 pub skipped: Vec<String>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct FsListDirRequest {
306 pub path: String,
308 #[serde(default)]
309 pub show_hidden: bool,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct FsListDirResponse {
315 pub entries: Vec<FsEntry>,
316 pub cwd: String,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct FsEntry {
322 pub name: String,
323 pub full_path: String,
324 pub is_dir: bool,
325 pub is_hidden: bool,
326 pub size: u64,
327 #[serde(skip_serializing_if = "Option::is_none")]
328 pub mtime: Option<u64>,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct FsStatResult {
334 pub name: String,
335 pub full_path: String,
336 pub is_dir: bool,
337 pub is_hidden: bool,
338 pub size: u64,
339 pub mtime: u64,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub mode: Option<u32>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct FsStatError {
347 pub path: String,
348 pub code: &'static str, pub message: String,
350}