use std::ffi::{c_char, c_void};
use std::sync::Arc;
use async_trait::async_trait;
use blazen_uniffi::errors::{BlazenError as InnerError, BlazenResult};
use blazen_uniffi::workflow::{Event as InnerEvent, StepHandler, StepOutput as InnerStepOutput};
use crate::error::BlazenError;
use crate::string::cstr_to_str;
use crate::workflow::BlazenWorkflowBuilder;
use crate::workflow_records::{BlazenEvent, BlazenStepOutput};
unsafe fn write_error(out_err: *mut *mut BlazenError, e: InnerError) -> i32 {
if !out_err.is_null() {
unsafe {
*out_err = BlazenError::from(e).into_ptr();
}
}
-1
}
unsafe fn write_internal_error(out_err: *mut *mut BlazenError, msg: &str) -> i32 {
unsafe {
write_error(
out_err,
InnerError::Internal {
message: msg.into(),
},
)
}
}
unsafe fn cstr_array_to_vec_string(
ptrs: *const *const c_char,
count: usize,
) -> Option<Vec<String>> {
if count == 0 {
return Some(Vec::new());
}
if ptrs.is_null() {
return None;
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let item = unsafe { *ptrs.add(i) };
let s = unsafe { cstr_to_str(item) }?;
out.push(s.to_owned());
}
Some(out)
}
#[repr(C)]
pub struct BlazenStepHandlerVTable {
pub user_data: *mut c_void,
pub drop_user_data: extern "C" fn(user_data: *mut c_void),
pub invoke: extern "C" fn(
user_data: *mut c_void,
event: *mut BlazenEvent,
out_output: *mut *mut BlazenStepOutput,
out_err: *mut *mut BlazenError,
) -> i32,
}
unsafe impl Send for BlazenStepHandlerVTable {}
unsafe impl Sync for BlazenStepHandlerVTable {}
pub(crate) struct CStepHandler {
vtable: BlazenStepHandlerVTable,
}
impl Drop for CStepHandler {
fn drop(&mut self) {
(self.vtable.drop_user_data)(self.vtable.user_data);
}
}
#[async_trait]
impl StepHandler for CStepHandler {
#[allow(clippy::result_large_err)]
async fn invoke(&self, event: InnerEvent) -> BlazenResult<InnerStepOutput> {
let event_ptr = BlazenEvent::from(event).into_ptr();
let user_data_addr = self.vtable.user_data as usize;
let invoke_fn = self.vtable.invoke;
let event_addr = event_ptr as usize;
let join_result =
tokio::task::spawn_blocking(move || -> Result<InnerStepOutput, InnerError> {
let user_data = user_data_addr as *mut c_void;
let event_ptr = event_addr as *mut BlazenEvent;
let mut out_output: *mut BlazenStepOutput = std::ptr::null_mut();
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = invoke_fn(user_data, event_ptr, &raw mut out_output, &raw mut out_err);
if status == 0 {
if out_output.is_null() {
return Err(InnerError::Internal {
message: "step handler returned success but null output".into(),
});
}
let bs = unsafe { Box::from_raw(out_output) };
Ok(bs.0)
} else {
if out_err.is_null() {
return Err(InnerError::Internal {
message: "step handler returned -1 without setting out_err".into(),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match join_result {
Ok(Ok(out)) => Ok(out),
Ok(Err(e)) => Err(e),
Err(join_err) => Err(InnerError::Internal {
message: format!("step handler task panicked: {join_err}"),
}),
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_workflow_builder_add_step(
builder: *mut BlazenWorkflowBuilder,
name: *const c_char,
accepts: *const *const c_char,
accepts_count: usize,
emits: *const *const c_char,
emits_count: usize,
vtable: BlazenStepHandlerVTable,
out_err: *mut *mut BlazenError,
) -> i32 {
if builder.is_null() {
(vtable.drop_user_data)(vtable.user_data);
return unsafe { write_internal_error(out_err, "null builder pointer") };
}
let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
(vtable.drop_user_data)(vtable.user_data);
return unsafe { write_internal_error(out_err, "null or non-UTF-8 step name") };
};
let name_owned = name_str.to_owned();
let Some(accepts_vec) = (unsafe { cstr_array_to_vec_string(accepts, accepts_count) }) else {
(vtable.drop_user_data)(vtable.user_data);
return unsafe { write_internal_error(out_err, "null or non-UTF-8 accepts entry") };
};
let Some(emits_vec) = (unsafe { cstr_array_to_vec_string(emits, emits_count) }) else {
(vtable.drop_user_data)(vtable.user_data);
return unsafe { write_internal_error(out_err, "null or non-UTF-8 emits entry") };
};
let handler: Arc<dyn StepHandler> = Arc::new(CStepHandler { vtable });
let builder_ref = unsafe { &*builder };
let inner = Arc::clone(&builder_ref.0);
match inner.step(name_owned, accepts_vec, emits_vec, handler) {
Ok(_) => 0,
Err(e) => unsafe { write_error(out_err, e) },
}
}