#[cfg(applefm_bridge)]
mod real {
use std::ffi::{CStr, CString, c_char, c_void};
unsafe extern "C" {
fn afm_availability() -> *mut c_char;
fn afm_complete(request_json: *const c_char) -> *mut c_char;
fn afm_respond_stream(
request_json: *const c_char,
on_event: Option<unsafe extern "C" fn(*mut c_void, *const c_char)>,
context: *mut c_void,
);
fn afm_string_free(ptr: *mut c_char);
}
unsafe fn take_bridge_string(ptr: *mut c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
unsafe {
let s = CStr::from_ptr(ptr).to_string_lossy().into_owned();
afm_string_free(ptr);
Some(s)
}
}
pub fn availability_json() -> String {
unsafe { take_bridge_string(afm_availability()) }
.unwrap_or_else(|| r#"{"available":false,"reason":"bridge returned null"}"#.to_owned())
}
pub fn complete_json(request: &str) -> String {
let Ok(request) = CString::new(request) else {
return r#"{"error":{"kind":"decode","message":"request contained a NUL byte"}}"#
.to_owned();
};
unsafe { take_bridge_string(afm_complete(request.as_ptr())) }.unwrap_or_else(|| {
r#"{"error":{"kind":"internal","message":"bridge returned null"}}"#.to_owned()
})
}
pub fn stream_json(request: &str, on_event: impl FnMut(&str) + Send) {
let mut on_event: Box<dyn FnMut(&str) + Send> = Box::new(on_event);
let Ok(request) = CString::new(request) else {
on_event(
r#"{"type":"error","error":{"kind":"decode","message":"request contained a NUL byte"}}"#,
);
return;
};
unsafe extern "C" fn trampoline(context: *mut c_void, event: *const c_char) {
if context.is_null() || event.is_null() {
return;
}
unsafe {
let on_event = &mut *context.cast::<Box<dyn FnMut(&str) + Send>>();
let event = CStr::from_ptr(event).to_string_lossy();
on_event(&event);
}
}
let context = (&raw mut on_event).cast::<c_void>();
unsafe { afm_respond_stream(request.as_ptr(), Some(trampoline), context) }
}
}
#[cfg(not(applefm_bridge))]
mod stub {
const STUB_REASON: &str = "chat-applefm was built without the Swift bridge (non-macOS target, docs build, or APPLEFM_SKIP_BRIDGE set)";
pub fn availability_json() -> String {
format!(r#"{{"available":false,"reason":"{STUB_REASON}"}}"#)
}
pub fn complete_json(_request: &str) -> String {
format!(r#"{{"error":{{"kind":"unavailable","message":"{STUB_REASON}"}}}}"#)
}
pub fn stream_json(_request: &str, mut on_event: impl FnMut(&str) + Send) {
on_event(&format!(
r#"{{"type":"error","error":{{"kind":"unavailable","message":"{STUB_REASON}"}}}}"#
));
}
}
#[cfg(applefm_bridge)]
pub(crate) use real::{availability_json, complete_json, stream_json};
#[cfg(not(applefm_bridge))]
pub(crate) use stub::{availability_json, complete_json, stream_json};