mockforge_sdk/
ffi.rs

1//! FFI bindings for using MockForge from other languages (Python, Node.js, Go)
2//!
3//! This module provides C-compatible functions that can be called from other languages.
4
5#![allow(unsafe_code)]
6
7use crate::server::MockServer;
8use std::ffi::{CStr, CString};
9use std::os::raw::c_char;
10use std::ptr;
11use std::sync::Arc;
12use tokio::runtime::Runtime;
13use tokio::sync::Mutex;
14
15/// Opaque handle to a MockServer
16pub struct MockServerHandle {
17    server: Arc<Mutex<MockServer>>,
18    runtime: Runtime,
19}
20
21/// Create a new mock server
22///
23/// # Safety
24/// This function is FFI-safe
25#[no_mangle]
26pub unsafe extern "C" fn mockforge_server_new(port: u16) -> *mut MockServerHandle {
27    let runtime = match Runtime::new() {
28        Ok(rt) => rt,
29        Err(_) => return ptr::null_mut(),
30    };
31
32    // Create and start the server
33    let server = runtime.block_on(async { MockServer::new().port(port).start().await });
34
35    let server = match server {
36        Ok(s) => s,
37        Err(_) => return ptr::null_mut(),
38    };
39
40    let handle = MockServerHandle {
41        server: Arc::new(Mutex::new(server)),
42        runtime,
43    };
44
45    Box::into_raw(Box::new(handle))
46}
47
48/// Stop and destroy a mock server
49///
50/// # Safety
51/// The handle must be valid and not used after this call
52#[no_mangle]
53pub unsafe extern "C" fn mockforge_server_destroy(handle: *mut MockServerHandle) {
54    if handle.is_null() {
55        return;
56    }
57
58    let handle = Box::from_raw(handle);
59    let server = handle.server.clone();
60
61    handle.runtime.block_on(async move {
62        let mut server = server.lock().await;
63        let _ = std::mem::take(&mut *server).stop().await;
64    });
65}
66
67/// Add a stub response to the mock server
68///
69/// # Safety
70/// - handle must be valid
71/// - method, path, and body must be valid null-terminated C strings
72/// - Returns 0 on success, -1 on error
73#[no_mangle]
74pub unsafe extern "C" fn mockforge_server_stub(
75    handle: *mut MockServerHandle,
76    method: *const c_char,
77    path: *const c_char,
78    status: u16,
79    body: *const c_char,
80) -> i32 {
81    if handle.is_null() || method.is_null() || path.is_null() || body.is_null() {
82        return -1;
83    }
84
85    let handle = &*handle;
86
87    let method = match CStr::from_ptr(method).to_str() {
88        Ok(s) => s,
89        Err(_) => return -1,
90    };
91
92    let path = match CStr::from_ptr(path).to_str() {
93        Ok(s) => s,
94        Err(_) => return -1,
95    };
96
97    let body = match CStr::from_ptr(body).to_str() {
98        Ok(s) => s,
99        Err(_) => return -1,
100    };
101
102    let body_value: serde_json::Value = match serde_json::from_str(body) {
103        Ok(v) => v,
104        Err(_) => return -1,
105    };
106
107    let server = handle.server.clone();
108    let result = handle.runtime.block_on(async move {
109        let mut server = server.lock().await;
110        server.stub_response(method, path, body_value).await
111    });
112
113    match result {
114        Ok(_) => 0,
115        Err(_) => -1,
116    }
117}
118
119/// Get the server URL
120///
121/// # Safety
122/// - handle must be valid
123/// - Returns a C string that must be freed with mockforge_free_string
124#[no_mangle]
125pub unsafe extern "C" fn mockforge_server_url(handle: *const MockServerHandle) -> *mut c_char {
126    if handle.is_null() {
127        return ptr::null_mut();
128    }
129
130    let handle = &*handle;
131    let server = handle.server.clone();
132
133    let url = handle.runtime.block_on(async move {
134        let server = server.lock().await;
135        server.url()
136    });
137
138    match CString::new(url) {
139        Ok(s) => s.into_raw(),
140        Err(_) => ptr::null_mut(),
141    }
142}
143
144/// Free a string returned by MockForge
145///
146/// # Safety
147/// The string must have been allocated by MockForge
148#[no_mangle]
149pub unsafe extern "C" fn mockforge_free_string(s: *mut c_char) {
150    if !s.is_null() {
151        let _ = CString::from_raw(s);
152    }
153}
154
155/// Get the last error message
156///
157/// # Safety
158/// Returns a C string that must be freed with mockforge_free_string
159#[no_mangle]
160pub unsafe extern "C" fn mockforge_last_error() -> *mut c_char {
161    // Thread-local error storage could be implemented here
162    ptr::null_mut()
163}