1use crate::types::HttpMethod;
7
8use std::{
9 collections::HashMap,
10 ffi::{CStr, CString},
11 os::raw::{c_char, c_int},
12 ptr,
13 sync::{Mutex, OnceLock},
14};
15
16static FFI_CONTEXT: OnceLock<Mutex<FfiContext>> = OnceLock::new();
18
19pub struct FfiContext {
21 servers: HashMap<u32, Box<dyn FfiServer>>,
22 next_id: u32,
23 last_error: Option<String>,
24}
25
26impl FfiContext {
27 fn new() -> Self {
28 Self {
29 servers: HashMap::new(),
30 next_id: 1,
31 last_error: None,
32 }
33 }
34
35 fn add_server(&mut self, server: Box<dyn FfiServer>) -> u32 {
36 let id = self.next_id;
37 self.next_id += 1;
38 self.servers.insert(id, server);
39 id
40 }
41
42 fn get_server(&mut self, id: u32) -> Option<&mut Box<dyn FfiServer>> {
43 self.servers.get_mut(&id)
44 }
45
46 fn remove_server(&mut self, id: u32) -> Option<Box<dyn FfiServer>> {
47 self.servers.remove(&id)
48 }
49
50 fn set_error(&mut self, error: String) {
51 self.last_error = Some(error);
52 }
53
54 fn get_last_error(&self) -> Option<&String> {
55 self.last_error.as_ref()
56 }
57}
58
59trait FfiServer: Send + Sync {
61 fn add_route(&mut self, path: &str, method: HttpMethod, handler_id: u32) -> Result<(), String>;
62 fn bind(&mut self, addr: &str) -> Result<(), String>;
63 fn start(&mut self) -> Result<(), String>;
64 fn stop(&mut self) -> Result<(), String>;
65 fn is_running(&self) -> bool;
66}
67
68struct MockFfiServer {
70 routes: Vec<(String, HttpMethod, u32)>,
71 address: Option<String>,
72 running: bool,
73}
74
75impl MockFfiServer {
76 fn new() -> Self {
77 Self {
78 routes: Vec::new(),
79 address: None,
80 running: false,
81 }
82 }
83}
84
85impl FfiServer for MockFfiServer {
86 fn add_route(&mut self, path: &str, method: HttpMethod, handler_id: u32) -> Result<(), String> {
87 self.routes.push((path.to_string(), method, handler_id));
88 Ok(())
89 }
90
91 fn bind(&mut self, addr: &str) -> Result<(), String> {
92 self.address = Some(addr.to_string());
93 Ok(())
94 }
95
96 fn start(&mut self) -> Result<(), String> {
97 if self.address.is_none() {
98 return Err("Server not bound to an address".to_string());
99 }
100 self.running = true;
101 Ok(())
102 }
103
104 fn stop(&mut self) -> Result<(), String> {
105 self.running = false;
106 Ok(())
107 }
108
109 fn is_running(&self) -> bool {
110 self.running
111 }
112}
113
114fn get_ffi_context() -> std::sync::MutexGuard<'static, FfiContext> {
116 FFI_CONTEXT
117 .get_or_init(|| Mutex::new(FfiContext::new()))
118 .lock()
119 .unwrap()
120}
121
122unsafe fn c_str_to_string(c_str: *const c_char) -> Result<String, String> {
124 if c_str.is_null() {
125 return Err("Null pointer provided".to_string());
126 }
127
128 unsafe {
129 match CStr::from_ptr(c_str).to_str() {
130 Ok(s) => Ok(s.to_string()),
131 Err(_) => Err("Invalid UTF-8 string".to_string()),
132 }
133 }
134}
135
136fn string_to_c_str(s: &str) -> *mut c_char {
138 match CString::new(s) {
139 Ok(c_string) => c_string.into_raw(),
140 Err(_) => ptr::null_mut(),
141 }
142}
143
144#[allow(dead_code)]
146fn http_method_to_c_int(method: HttpMethod) -> c_int {
147 match method {
148 HttpMethod::GET => 0,
149 HttpMethod::POST => 1,
150 HttpMethod::PUT => 2,
151 HttpMethod::DELETE => 3,
152 HttpMethod::PATCH => 4,
153 HttpMethod::HEAD => 5,
154 HttpMethod::OPTIONS => 6,
155 HttpMethod::TRACE => 7,
156 HttpMethod::CONNECT => 8,
157 }
158}
159
160fn c_int_to_http_method(method: c_int) -> Result<HttpMethod, String> {
162 match method {
163 0 => Ok(HttpMethod::GET),
164 1 => Ok(HttpMethod::POST),
165 2 => Ok(HttpMethod::PUT),
166 3 => Ok(HttpMethod::DELETE),
167 4 => Ok(HttpMethod::PATCH),
168 5 => Ok(HttpMethod::HEAD),
169 6 => Ok(HttpMethod::OPTIONS),
170 7 => Ok(HttpMethod::TRACE),
171 8 => Ok(HttpMethod::CONNECT),
172 _ => Err(format!("Invalid HTTP method: {}", method)),
173 }
174}
175
176pub const FFI_SUCCESS: c_int = 0;
178pub const FFI_ERROR_NULL_POINTER: c_int = -1;
179pub const FFI_ERROR_INVALID_ARGUMENT: c_int = -2;
180pub const FFI_ERROR_SERVER_NOT_FOUND: c_int = -3;
181pub const FFI_ERROR_INTERNAL: c_int = -4;
182
183#[unsafe(no_mangle)]
190pub extern "C" fn ws_ffi_init() -> c_int {
191 FFI_SUCCESS
193}
194
195#[unsafe(no_mangle)]
198pub extern "C" fn ws_create_server() -> c_int {
199 let mut context = get_ffi_context();
200 let server = Box::new(MockFfiServer::new());
201 let id = context.add_server(server);
202 id as c_int
203}
204
205#[unsafe(no_mangle)]
208pub unsafe extern "C" fn ws_bind_server(server_id: c_int, address: *const c_char) -> c_int {
209 let mut context = get_ffi_context();
210
211 let addr = match unsafe { c_str_to_string(address) } {
212 Ok(s) => s,
213 Err(e) => {
214 context.set_error(e);
215 return FFI_ERROR_INVALID_ARGUMENT;
216 }
217 };
218
219 match context.get_server(server_id as u32) {
220 Some(server) => match server.bind(&addr) {
221 Ok(()) => FFI_SUCCESS,
222 Err(e) => {
223 context.set_error(e);
224 FFI_ERROR_INTERNAL
225 }
226 },
227 None => {
228 context.set_error("Server not found".to_string());
229 FFI_ERROR_SERVER_NOT_FOUND
230 }
231 }
232}
233
234#[unsafe(no_mangle)]
237pub unsafe extern "C" fn ws_add_route(
238 server_id: c_int,
239 path: *const c_char,
240 method: c_int,
241 handler_id: c_int,
242) -> c_int {
243 let mut context = get_ffi_context();
244
245 let path_str = match unsafe { c_str_to_string(path) } {
246 Ok(s) => s,
247 Err(e) => {
248 context.set_error(e);
249 return FFI_ERROR_INVALID_ARGUMENT;
250 }
251 };
252
253 let http_method = match c_int_to_http_method(method) {
254 Ok(m) => m,
255 Err(e) => {
256 context.set_error(e);
257 return FFI_ERROR_INVALID_ARGUMENT;
258 }
259 };
260
261 match context.get_server(server_id as u32) {
262 Some(server) => match server.add_route(&path_str, http_method, handler_id as u32) {
263 Ok(()) => FFI_SUCCESS,
264 Err(e) => {
265 context.set_error(e);
266 FFI_ERROR_INTERNAL
267 }
268 },
269 None => {
270 context.set_error("Server not found".to_string());
271 FFI_ERROR_SERVER_NOT_FOUND
272 }
273 }
274}
275
276#[unsafe(no_mangle)]
279pub extern "C" fn ws_start_server(server_id: c_int) -> c_int {
280 let mut context = get_ffi_context();
281
282 match context.get_server(server_id as u32) {
283 Some(server) => match server.start() {
284 Ok(()) => FFI_SUCCESS,
285 Err(e) => {
286 context.set_error(e);
287 FFI_ERROR_INTERNAL
288 }
289 },
290 None => {
291 context.set_error("Server not found".to_string());
292 FFI_ERROR_SERVER_NOT_FOUND
293 }
294 }
295}
296
297#[unsafe(no_mangle)]
300pub extern "C" fn ws_stop_server(server_id: c_int) -> c_int {
301 let mut context = get_ffi_context();
302
303 match context.get_server(server_id as u32) {
304 Some(server) => match server.stop() {
305 Ok(()) => FFI_SUCCESS,
306 Err(e) => {
307 context.set_error(e);
308 FFI_ERROR_INTERNAL
309 }
310 },
311 None => {
312 context.set_error("Server not found".to_string());
313 FFI_ERROR_SERVER_NOT_FOUND
314 }
315 }
316}
317
318#[unsafe(no_mangle)]
321pub extern "C" fn ws_is_server_running(server_id: c_int) -> c_int {
322 let mut context = get_ffi_context();
323
324 match context.get_server(server_id as u32) {
325 Some(server) => {
326 if server.is_running() {
327 1
328 } else {
329 0
330 }
331 }
332 None => {
333 context.set_error("Server not found".to_string());
334 FFI_ERROR_SERVER_NOT_FOUND
335 }
336 }
337}
338
339#[unsafe(no_mangle)]
342pub extern "C" fn ws_destroy_server(server_id: c_int) -> c_int {
343 let mut context = get_ffi_context();
344
345 match context.remove_server(server_id as u32) {
346 Some(_) => FFI_SUCCESS,
347 None => {
348 context.set_error("Server not found".to_string());
349 FFI_ERROR_SERVER_NOT_FOUND
350 }
351 }
352}
353
354#[unsafe(no_mangle)]
358pub extern "C" fn ws_get_last_error() -> *mut c_char {
359 let context = get_ffi_context();
360
361 match context.get_last_error() {
362 Some(error) => string_to_c_str(error),
363 None => ptr::null_mut(),
364 }
365}
366
367#[unsafe(no_mangle)]
369pub unsafe extern "C" fn ws_free_string(s: *mut c_char) {
370 if !s.is_null() {
371 unsafe {
372 let _ = CString::from_raw(s);
373 }
374 }
375}
376
377#[unsafe(no_mangle)]
380pub extern "C" fn ws_get_version() -> *mut c_char {
381 string_to_c_str(env!("CARGO_PKG_VERSION"))
382}
383
384#[unsafe(no_mangle)]
386pub extern "C" fn ws_ffi_cleanup() -> c_int {
387 FFI_SUCCESS
389}
390
391#[repr(C)]
397pub struct PythonServerHandle {
398 pub server_id: c_int,
399 pub is_valid: c_int,
400}
401
402#[unsafe(no_mangle)]
404pub extern "C" fn ws_create_python_server() -> PythonServerHandle {
405 let server_id = ws_create_server();
406 PythonServerHandle {
407 server_id,
408 is_valid: if server_id >= 0 { 1 } else { 0 },
409 }
410}
411
412#[unsafe(no_mangle)]
414pub extern "C" fn ws_create_nodejs_server(
415 _callback: extern "C" fn(*const c_char, c_int, *const c_char) -> c_int,
416) -> c_int {
417 ws_create_server()
419}
420
421#[unsafe(no_mangle)]
423pub unsafe extern "C" fn ws_create_go_server(error_out: *mut *mut c_char) -> c_int {
424 let server_id = ws_create_server();
425
426 if server_id < 0 {
427 let error = ws_get_last_error();
428 if !error.is_null() {
429 unsafe {
430 *error_out = error;
431 }
432 }
433 } else {
434 unsafe {
435 *error_out = ptr::null_mut();
436 }
437 }
438
439 server_id
440}
441
442pub fn generate_c_header() -> String {
448 r#"
449#ifndef WEB_SERVER_ABSTRACTION_FFI_H
450#define WEB_SERVER_ABSTRACTION_FFI_H
451
452#ifdef __cplusplus
453extern "C" {
454#endif
455
456// Result codes
457#define FFI_SUCCESS 0
458#define FFI_ERROR_NULL_POINTER -1
459#define FFI_ERROR_INVALID_ARGUMENT -2
460#define FFI_ERROR_SERVER_NOT_FOUND -3
461#define FFI_ERROR_INTERNAL -4
462
463// HTTP methods
464#define HTTP_GET 0
465#define HTTP_POST 1
466#define HTTP_PUT 2
467#define HTTP_DELETE 3
468#define HTTP_PATCH 4
469#define HTTP_HEAD 5
470#define HTTP_OPTIONS 6
471#define HTTP_TRACE 7
472#define HTTP_CONNECT 8
473
474// Core functions
475int ws_ffi_init(void);
476int ws_create_server(void);
477int ws_bind_server(int server_id, const char* address);
478int ws_add_route(int server_id, const char* path, int method, int handler_id);
479int ws_start_server(int server_id);
480int ws_stop_server(int server_id);
481int ws_is_server_running(int server_id);
482int ws_destroy_server(int server_id);
483char* ws_get_last_error(void);
484void ws_free_string(char* s);
485char* ws_get_version(void);
486int ws_ffi_cleanup(void);
487
488// Language-specific helpers
489typedef struct {
490 int server_id;
491 int is_valid;
492} PythonServerHandle;
493
494PythonServerHandle ws_create_python_server(void);
495int ws_create_nodejs_server(int (*callback)(const char*, int, const char*));
496int ws_create_go_server(char** error_out);
497
498#ifdef __cplusplus
499}
500#endif
501
502#endif // WEB_SERVER_ABSTRACTION_FFI_H
503"#
504 .to_string()
505}
506
507pub fn save_c_header(path: &str) -> Result<(), std::io::Error> {
509 std::fs::write(path, generate_c_header())
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn test_ffi_server_lifecycle() {
518 assert_eq!(ws_ffi_init(), FFI_SUCCESS);
520
521 let server_id = ws_create_server();
523 assert!(server_id >= 0);
524
525 let addr = CString::new("127.0.0.1:8080").unwrap();
527 assert_eq!(
528 unsafe { ws_bind_server(server_id, addr.as_ptr()) },
529 FFI_SUCCESS
530 );
531
532 let path = CString::new("/test").unwrap();
534 assert_eq!(
535 unsafe { ws_add_route(server_id, path.as_ptr(), 0, 1) },
536 FFI_SUCCESS
537 );
538
539 assert_eq!(ws_start_server(server_id), FFI_SUCCESS);
541 assert_eq!(ws_is_server_running(server_id), 1);
542
543 assert_eq!(ws_stop_server(server_id), FFI_SUCCESS);
545 assert_eq!(ws_is_server_running(server_id), 0);
546
547 assert_eq!(ws_destroy_server(server_id), FFI_SUCCESS);
549
550 assert_eq!(ws_ffi_cleanup(), FFI_SUCCESS);
552 }
553
554 #[test]
555 fn test_ffi_error_handling() {
556 ws_ffi_init();
557
558 assert_eq!(ws_start_server(999), FFI_ERROR_SERVER_NOT_FOUND);
560
561 let error = ws_get_last_error();
563 assert!(!error.is_null());
564
565 unsafe { ws_free_string(error) };
567 }
568
569 #[test]
570 fn test_ffi_version() {
571 let version = ws_get_version();
572 assert!(!version.is_null());
573 unsafe { ws_free_string(version) };
574 }
575
576 #[test]
577 fn test_python_bindings() {
578 ws_ffi_init();
579 let handle = ws_create_python_server();
580 assert_eq!(handle.is_valid, 1);
581 assert!(handle.server_id >= 0);
582 ws_destroy_server(handle.server_id);
583 }
584
585 #[test]
586 fn test_c_header_generation() {
587 let header = generate_c_header();
588 assert!(header.contains("#ifndef WEB_SERVER_ABSTRACTION_FFI_H"));
589 assert!(header.contains("int ws_create_server(void);"));
590 assert!(header.contains("#define FFI_SUCCESS 0"));
591 }
592}