impl {{ core_import }}::{{ trait_name }} for {{ bridge_name }} {
fn {{ dispatch_name }}(
&self{{ extra_param }},
{{ wire_name }}: {{ req_path }},
) -> std::pin::Pin<Box<dyn std::future::Future<Output = {{ output_type }}> + Send + '_>> {
Box::pin(async move {
let outcome: {{ wire_output }} = async move {
// Serialize request to JSON
let req_json = serde_json::to_string(&{{ wire_name }})
.map_err(|e| Box::new(e) as {{ box_err }})?;
let req_c_str = CString::new(req_json)
.map_err(|e| Box::new(e) as {{ box_err }})?;
// Call the C callback on a blocking thread to avoid stalling the async executor.
// Raw pointers are not `Send`, so the context and result pointers cross the
// spawn_blocking boundary as `usize`; the owned `CString` moves in to stay alive.
let callback = self.callback;
let context = self.context as usize;
let resp_addr = tokio::task::spawn_blocking(move || {
(callback)(context as *mut c_void, req_c_str.as_ptr()) as usize
})
.await
.map_err(|e| Box::new(e) as {{ box_err }})?;
let resp_ptr = resp_addr as *mut c_char;
if resp_ptr.is_null() {
return Err("C callback returned null response".into());
}
// SAFETY: resp_ptr was returned by the C callback and must be a null-terminated string.
let resp_c_str = unsafe { CStr::from_ptr(resp_ptr) };
let resp_json = resp_c_str.to_string_lossy();
// Deserialize response from JSON
let response: {{ resp_path }} = serde_json::from_str(&resp_json)
.map_err(|e| Box::new(e) as {{ box_err }})?;
// Free the C-allocated response string. The host allocates it via malloc/strdup
// and hands ownership to us; we release it with the C runtime's free.
// SAFETY: resp_ptr is null-checked above and was produced by the host callback.
unsafe {
extern "C" {
fn free(ptr: *mut c_void);
}
free(resp_ptr as *mut c_void);
}
Ok(response)
}
.await;
{{ tail }}
})
}
}