Skip to main content

routa_core/rpc/
router.rs

1//! Transport-agnostic JSON-RPC 2.0 dispatcher.
2//!
3//! `RpcRouter` takes an `AppState` and dispatches incoming JSON-RPC requests
4//! to the appropriate method handler. It is intentionally free of any HTTP
5//! or framework dependency so it can be used from:
6//!
7//! - An axum handler (HTTP)
8//! - A Tauri command (IPC)
9//! - A napi-rs / wasm-bindgen function (JS bindgen)
10//! - Stdio (CLI)
11
12use crate::state::AppState;
13
14use super::error::RpcError;
15use super::methods;
16use super::types::*;
17
18/// Transport-agnostic JSON-RPC router.
19///
20/// # Usage
21///
22/// ```ignore
23/// let router = RpcRouter::new(app_state);
24///
25/// // From raw JSON string:
26/// let response_json = router.handle_request(raw_json_str).await;
27///
28/// // From a parsed request:
29/// let response = router.dispatch(request).await;
30/// ```
31#[derive(Clone)]
32pub struct RpcRouter {
33    state: AppState,
34}
35
36impl RpcRouter {
37    /// Create a new router backed by the given application state.
38    pub fn new(state: AppState) -> Self {
39        Self { state }
40    }
41
42    /// Handle a raw JSON string. Parses the request, dispatches it, and returns
43    /// the serialized JSON response string.
44    pub async fn handle_request(&self, raw: &str) -> String {
45        // Try to parse as a batch request first
46        if let Ok(batch) = serde_json::from_str::<Vec<JsonRpcRequest>>(raw) {
47            let mut responses = Vec::with_capacity(batch.len());
48            for req in batch {
49                responses.push(self.dispatch(req).await);
50            }
51            return serde_json::to_string(&responses).unwrap_or_else(|_| {
52                r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Failed to serialize response"},"id":null}"#.into()
53            });
54        }
55
56        // Parse as single request
57        let request: JsonRpcRequest = match serde_json::from_str(raw) {
58            Ok(req) => req,
59            Err(e) => {
60                return serde_json::to_string(&JsonRpcResponse::error(
61                    None,
62                    PARSE_ERROR,
63                    format!("Parse error: {}", e),
64                ))
65                .unwrap_or_default();
66            }
67        };
68
69        let response = self.dispatch(request).await;
70        serde_json::to_string(&response).unwrap_or_else(|_| {
71            r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Failed to serialize response"},"id":null}"#.into()
72        })
73    }
74
75    /// Handle a pre-parsed `serde_json::Value`. Useful for transports that
76    /// already do their own parsing (e.g. Tauri IPC, axum JSON extraction).
77    pub async fn handle_value(&self, value: serde_json::Value) -> serde_json::Value {
78        let request: JsonRpcRequest = match serde_json::from_value(value) {
79            Ok(req) => req,
80            Err(e) => {
81                return serde_json::to_value(JsonRpcResponse::error(
82                    None,
83                    PARSE_ERROR,
84                    format!("Invalid request: {}", e),
85                ))
86                .unwrap_or_default();
87            }
88        };
89
90        let response = self.dispatch(request).await;
91        serde_json::to_value(response).unwrap_or_default()
92    }
93
94    /// Dispatch a parsed JSON-RPC request to the correct method handler.
95    pub async fn dispatch(&self, req: JsonRpcRequest) -> JsonRpcResponse {
96        // Validate JSON-RPC version
97        if req.jsonrpc != "2.0" {
98            return JsonRpcResponse::error(
99                req.id,
100                INVALID_REQUEST,
101                "Invalid JSON-RPC version, expected \"2.0\"",
102            );
103        }
104
105        let id = req.id.clone();
106        let params = req
107            .params
108            .unwrap_or(serde_json::Value::Object(Default::default()));
109
110        match self.route(&req.method, params).await {
111            Ok(result) => JsonRpcResponse::success(id, result),
112            Err(err) => err.to_response(id),
113        }
114    }
115
116    /// Route a method call to the correct handler and return the result as JSON.
117    async fn route(
118        &self,
119        method: &str,
120        params: serde_json::Value,
121    ) -> Result<serde_json::Value, RpcError> {
122        match method {
123            // ----- Agents -----
124            "agents.list" => {
125                let p = parse_params(params)?;
126                let r = methods::agents::list(&self.state, p).await?;
127                Ok(serde_json::to_value(r).unwrap())
128            }
129            "agents.get" => {
130                let p = parse_params(params)?;
131                let r = methods::agents::get(&self.state, p).await?;
132                Ok(serde_json::to_value(r).unwrap())
133            }
134            "agents.create" => {
135                let p = parse_params(params)?;
136                let r = methods::agents::create(&self.state, p).await?;
137                Ok(serde_json::to_value(r).unwrap())
138            }
139            "agents.delete" => {
140                let p = parse_params(params)?;
141                let r = methods::agents::delete(&self.state, p).await?;
142                Ok(serde_json::to_value(r).unwrap())
143            }
144            "agents.updateStatus" => {
145                let p = parse_params(params)?;
146                let r = methods::agents::update_status(&self.state, p).await?;
147                Ok(serde_json::to_value(r).unwrap())
148            }
149
150            // ----- Tasks -----
151            "tasks.list" => {
152                let p = parse_params(params)?;
153                let r = methods::tasks::list(&self.state, p).await?;
154                Ok(serde_json::to_value(r).unwrap())
155            }
156            "tasks.get" => {
157                let p = parse_params(params)?;
158                let r = methods::tasks::get(&self.state, p).await?;
159                Ok(serde_json::to_value(r).unwrap())
160            }
161            "tasks.create" => {
162                let p = parse_params(params)?;
163                let r = methods::tasks::create(&self.state, p).await?;
164                Ok(serde_json::to_value(r).unwrap())
165            }
166            "tasks.delete" => {
167                let p = parse_params(params)?;
168                let r = methods::tasks::delete(&self.state, p).await?;
169                Ok(serde_json::to_value(r).unwrap())
170            }
171            "tasks.updateStatus" => {
172                let p = parse_params(params)?;
173                let r = methods::tasks::update_status(&self.state, p).await?;
174                Ok(serde_json::to_value(r).unwrap())
175            }
176            "tasks.findReady" => {
177                let p = parse_params(params)?;
178                let r = methods::tasks::find_ready(&self.state, p).await?;
179                Ok(serde_json::to_value(r).unwrap())
180            }
181            "tasks.listArtifacts" => {
182                let p = parse_params(params)?;
183                let r = methods::tasks::list_artifacts(&self.state, p).await?;
184                Ok(serde_json::to_value(r).unwrap())
185            }
186            "tasks.provideArtifact" => {
187                let p = parse_params(params)?;
188                let r = methods::tasks::provide_artifact(&self.state, p).await?;
189                Ok(serde_json::to_value(r).unwrap())
190            }
191
192            // ----- Kanban -----
193            "kanban.listBoards" => {
194                let p = parse_params(params)?;
195                let r = methods::kanban::list_boards(&self.state, p).await?;
196                Ok(serde_json::to_value(r).unwrap())
197            }
198            "kanban.createBoard" => {
199                let p = parse_params(params)?;
200                let r = methods::kanban::create_board(&self.state, p).await?;
201                Ok(serde_json::to_value(r).unwrap())
202            }
203            "kanban.getBoard" => {
204                let p = parse_params(params)?;
205                let r = methods::kanban::get_board(&self.state, p).await?;
206                Ok(serde_json::to_value(r).unwrap())
207            }
208            "kanban.updateBoard" => {
209                let p = parse_params(params)?;
210                let r = methods::kanban::update_board(&self.state, p).await?;
211                Ok(serde_json::to_value(r).unwrap())
212            }
213            "kanban.createCard" => {
214                let p = parse_params(params)?;
215                let r = methods::kanban::create_card(&self.state, p).await?;
216                Ok(serde_json::to_value(r).unwrap())
217            }
218            "kanban.moveCard" => {
219                let p = parse_params(params)?;
220                let r = methods::kanban::move_card(&self.state, p).await?;
221                Ok(serde_json::to_value(r).unwrap())
222            }
223            "kanban.updateCard" => {
224                let p = parse_params(params)?;
225                let r = methods::kanban::update_card(&self.state, p).await?;
226                Ok(serde_json::to_value(r).unwrap())
227            }
228            "kanban.deleteCard" => {
229                let p = parse_params(params)?;
230                let r = methods::kanban::delete_card(&self.state, p).await?;
231                Ok(serde_json::to_value(r).unwrap())
232            }
233            "kanban.createColumn" => {
234                let p = parse_params(params)?;
235                let r = methods::kanban::create_column(&self.state, p).await?;
236                Ok(serde_json::to_value(r).unwrap())
237            }
238            "kanban.deleteColumn" => {
239                let p = parse_params(params)?;
240                let r = methods::kanban::delete_column(&self.state, p).await?;
241                Ok(serde_json::to_value(r).unwrap())
242            }
243            "kanban.searchCards" => {
244                let p = parse_params(params)?;
245                let r = methods::kanban::search_cards(&self.state, p).await?;
246                Ok(serde_json::to_value(r).unwrap())
247            }
248            "kanban.listCardsByColumn" => {
249                let p = parse_params(params)?;
250                let r = methods::kanban::list_cards_by_column(&self.state, p).await?;
251                Ok(serde_json::to_value(r).unwrap())
252            }
253            "kanban.decomposeTasks" => {
254                let p = parse_params(params)?;
255                let r = methods::kanban::decompose_tasks(&self.state, p).await?;
256                Ok(serde_json::to_value(r).unwrap())
257            }
258            "kanban.requestPreviousLaneHandoff" => {
259                let p = parse_params(params)?;
260                let r = methods::kanban::request_previous_lane_handoff(&self.state, p).await?;
261                Ok(serde_json::to_value(r).unwrap())
262            }
263            "kanban.submitLaneHandoff" => {
264                let p = parse_params(params)?;
265                let r = methods::kanban::submit_lane_handoff(&self.state, p).await?;
266                Ok(serde_json::to_value(r).unwrap())
267            }
268
269            // ----- Notes -----
270            "notes.list" => {
271                let p = parse_params(params)?;
272                let r = methods::notes::list(&self.state, p).await?;
273                Ok(serde_json::to_value(r).unwrap())
274            }
275            "notes.get" => {
276                let p = parse_params(params)?;
277                let r = methods::notes::get(&self.state, p).await?;
278                Ok(serde_json::to_value(r).unwrap())
279            }
280            "notes.create" => {
281                let p = parse_params(params)?;
282                let r = methods::notes::create(&self.state, p).await?;
283                Ok(serde_json::to_value(r).unwrap())
284            }
285            "notes.delete" => {
286                let p = parse_params(params)?;
287                let r = methods::notes::delete(&self.state, p).await?;
288                Ok(serde_json::to_value(r).unwrap())
289            }
290
291            // ----- Workspaces -----
292            "workspaces.list" => {
293                let r = methods::workspaces::list(&self.state).await?;
294                Ok(serde_json::to_value(r).unwrap())
295            }
296            "workspaces.get" => {
297                let p = parse_params(params)?;
298                let r = methods::workspaces::get(&self.state, p).await?;
299                Ok(serde_json::to_value(r).unwrap())
300            }
301            "workspaces.create" => {
302                let p = parse_params(params)?;
303                let r = methods::workspaces::create(&self.state, p).await?;
304                Ok(serde_json::to_value(r).unwrap())
305            }
306            "workspaces.delete" => {
307                let p = parse_params(params)?;
308                let r = methods::workspaces::delete(&self.state, p).await?;
309                Ok(serde_json::to_value(r).unwrap())
310            }
311
312            // ----- Skills -----
313            "skills.list" => {
314                let r = methods::skills::list(&self.state).await?;
315                Ok(serde_json::to_value(r).unwrap())
316            }
317            "skills.get" => {
318                let p = parse_params(params)?;
319                let r = methods::skills::get(&self.state, p).await?;
320                Ok(serde_json::to_value(r).unwrap())
321            }
322            "skills.reload" => {
323                let r = methods::skills::reload(&self.state).await?;
324                Ok(serde_json::to_value(r).unwrap())
325            }
326
327            // ----- Unknown method -----
328            _ => Err(RpcError::MethodNotFound(format!(
329                "Method not found: {}",
330                method
331            ))),
332        }
333    }
334
335    /// Return a list of all supported RPC method names.
336    /// Useful for introspection / discovery endpoints.
337    pub fn method_list(&self) -> Vec<&'static str> {
338        vec![
339            "agents.list",
340            "agents.get",
341            "agents.create",
342            "agents.delete",
343            "agents.updateStatus",
344            "tasks.list",
345            "tasks.get",
346            "tasks.create",
347            "tasks.delete",
348            "tasks.updateStatus",
349            "tasks.findReady",
350            "tasks.listArtifacts",
351            "tasks.provideArtifact",
352            "kanban.listBoards",
353            "kanban.createBoard",
354            "kanban.getBoard",
355            "kanban.updateBoard",
356            "kanban.createCard",
357            "kanban.moveCard",
358            "kanban.updateCard",
359            "kanban.deleteCard",
360            "kanban.createColumn",
361            "kanban.deleteColumn",
362            "kanban.searchCards",
363            "kanban.listCardsByColumn",
364            "kanban.decomposeTasks",
365            "notes.list",
366            "notes.get",
367            "notes.create",
368            "notes.delete",
369            "workspaces.list",
370            "workspaces.get",
371            "workspaces.create",
372            "workspaces.delete",
373            "skills.list",
374            "skills.get",
375            "skills.reload",
376        ]
377    }
378}
379
380/// Helper: deserialize `serde_json::Value` into a typed params struct.
381fn parse_params<T: serde::de::DeserializeOwned>(value: serde_json::Value) -> Result<T, RpcError> {
382    serde_json::from_value(value)
383        .map_err(|e| RpcError::InvalidParams(format!("Invalid params: {}", e)))
384}