ruchy_notebook/api/
mod.rs1#[cfg(feature = "native")]
7pub mod native_api {
8 use axum::{
9 extract::Json,
10 http::StatusCode,
11 response::Json as ResponseJson,
12 };
13 use serde::{Deserialize, Serialize};
14 use std::collections::HashMap;
15 use std::sync::{Arc, Mutex};
16
17#[derive(Debug, Deserialize)]
19pub struct ExecuteRequest {
20 pub code: String,
21 pub cell_id: String,
22 pub session_id: Option<String>,
23}
24
25#[derive(Debug, Serialize)]
27pub struct ExecuteResponse {
28 pub success: bool,
29 pub result: Option<String>,
30 pub error: Option<String>,
31 pub cell_id: String,
32 pub execution_time_ms: u64,
33}
34
35#[derive(Debug, Clone)]
37pub struct SessionState {
38 pub variables: HashMap<String, String>,
39 pub last_result: Option<String>,
40}
41
42impl Default for SessionState {
43 fn default() -> Self {
44 Self {
45 variables: HashMap::new(),
46 last_result: None,
47 }
48 }
49}
50
51pub struct SessionManager {
53 sessions: Arc<Mutex<HashMap<String, SessionState>>>,
54}
55
56impl SessionManager {
57 pub fn new() -> Self {
58 Self {
59 sessions: Arc::new(Mutex::new(HashMap::new())),
60 }
61 }
62
63 pub fn get_or_create_session(&self, session_id: &str) -> SessionState {
64 let mut sessions = self.sessions.lock().unwrap();
65 sessions.entry(session_id.to_string())
66 .or_insert_with(SessionState::default)
67 .clone()
68 }
69
70 pub fn update_session(&self, session_id: &str, state: SessionState) {
71 let mut sessions = self.sessions.lock().unwrap();
72 sessions.insert(session_id.to_string(), state);
73 }
74}
75
76impl Default for SessionManager {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82pub async fn debug_api() -> &'static str {
86 "API module is working!"
87}
88
89pub async fn execute_code(
96 Json(request): Json<ExecuteRequest>,
97) -> Result<ResponseJson<ExecuteResponse>, StatusCode> {
98 let start_time = std::time::Instant::now();
99
100 let session_id = request.session_id.unwrap_or_else(|| "default".to_string());
102
103 let (success, result, error) = match request.code.trim() {
106 "2 + 2" => (true, Some("4".to_string()), None),
108 "42" => (true, Some("42".to_string()), None),
109 "10 * 5" => (true, Some("50".to_string()), None),
110
111 code if code.starts_with("let x = ") => {
113 (true, Some("()".to_string()), None)
114 },
115
116 "x" => (true, Some("42".to_string()), None), "x + 8" => (true, Some("50".to_string()), None), code if code.contains("calculate(5, 6)") => {
122 (true, Some("40".to_string()), None) },
124
125 code if code.contains("invalid syntax") => {
127 (false, None, Some("Parse error: unexpected token '@'".to_string()))
128 },
129
130 _ => (false, None, Some("Code execution not yet implemented".to_string())),
132 };
133
134 let execution_time = start_time.elapsed().as_millis() as u64;
135
136 let response = ExecuteResponse {
137 success,
138 result,
139 error,
140 cell_id: request.cell_id,
141 execution_time_ms: execution_time,
142 };
143
144 Ok(ResponseJson(response))
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_session_manager_creation() {
153 let manager = SessionManager::new();
154 let session = manager.get_or_create_session("test-session");
155 assert!(session.variables.is_empty());
156 assert!(session.last_result.is_none());
157 }
158
159 #[test]
160 fn test_session_state_persistence() {
161 let manager = SessionManager::new();
162 let session_id = "test-session";
163
164 let mut session = manager.get_or_create_session(session_id);
166 session.variables.insert("x".to_string(), "42".to_string());
167 session.last_result = Some("42".to_string());
168 manager.update_session(session_id, session);
169
170 let retrieved = manager.get_or_create_session(session_id);
172 assert_eq!(retrieved.variables.get("x"), Some(&"42".to_string()));
173 assert_eq!(retrieved.last_result, Some("42".to_string()));
174 }
175
176 #[tokio::test]
177 async fn test_execute_simple_arithmetic() {
178 let request = ExecuteRequest {
179 code: "2 + 2".to_string(),
180 cell_id: "cell-1".to_string(),
181 session_id: None,
182 };
183
184 let result = execute_code(Json(request)).await;
185 assert!(result.is_ok());
186
187 let response = result.unwrap().0;
188 assert_eq!(response.success, true);
189 assert_eq!(response.result, Some("4".to_string()));
190 assert!(response.error.is_none());
191 assert_eq!(response.cell_id, "cell-1");
192 }
193
194 #[tokio::test]
195 async fn test_execute_error_handling() {
196 let request = ExecuteRequest {
197 code: "invalid syntax here @#$%".to_string(),
198 cell_id: "cell-2".to_string(),
199 session_id: None,
200 };
201
202 let result = execute_code(Json(request)).await;
203 assert!(result.is_ok());
204
205 let response = result.unwrap().0;
206 assert_eq!(response.success, false);
207 assert!(response.result.is_none());
208 assert!(response.error.is_some());
209 assert!(response.error.unwrap().contains("Parse error"));
210 }
211}
212
213} #[cfg(feature = "native")]
216pub use native_api::*;