1#![deny(missing_docs)]
3#![warn(clippy::missing_errors_doc)]
4#![warn(clippy::missing_panics_doc)]
5#![warn(clippy::doc_markdown)]
6#![allow(non_camel_case_types)]
7#![allow(unsafe_code)]
8#![allow(clippy::not_unsafe_ptr_arg_deref)]
9
10pub mod arena;
11pub mod convert;
12mod macros;
13
14pub use convert::{
15 lift_byte_slice, lift_bytes, lift_opt_scalar, lift_opt_string, lift_ptr_vec, lift_scalar_vec,
16 lift_string_vec, lower_bytes, lower_opt_scalar, lower_opt_string, lower_ptr_vec,
17 lower_scalar_vec, lower_string_vec, write_map_out,
18};
19
20use std::ffi::{CStr, CString};
21use std::os::raw::c_char;
22use std::ptr;
23use std::sync::atomic::{AtomicBool, Ordering};
24
25pub type weaveffi_handle_t = u64;
27
28#[repr(C)]
37#[derive(Debug)]
38pub struct weaveffi_error {
39 pub code: i32,
41 pub message: *const c_char,
44}
45
46impl Default for weaveffi_error {
47 fn default() -> Self {
48 Self {
49 code: 0,
50 message: ptr::null(),
51 }
52 }
53}
54
55pub fn error_set_ok(out_err: *mut weaveffi_error) {
57 if out_err.is_null() {
58 return;
59 }
60 let err = unsafe { &mut *out_err };
62 if !err.message.is_null() {
63 unsafe { drop(CString::from_raw(err.message as *mut c_char)) };
65 }
66 err.code = 0;
67 err.message = ptr::null();
68}
69
70#[allow(clippy::missing_panics_doc)]
74pub fn error_set(out_err: *mut weaveffi_error, code: i32, message: &str) {
75 if out_err.is_null() {
76 return;
77 }
78 let err = unsafe { &mut *out_err };
80 if !err.message.is_null() {
81 unsafe { drop(CString::from_raw(err.message as *mut c_char)) };
83 }
84 err.code = code;
85 let owned_message = message.replace('\0', "");
86 let cstr = CString::new(owned_message).expect("CString::new sanitized input");
87 err.message = cstr.into_raw();
88}
89
90pub trait ErrorReport {
129 fn code(&self) -> i32 {
132 -1
133 }
134
135 fn message(&self) -> String;
137}
138
139impl<E: std::fmt::Display> ErrorReport for E {
140 fn message(&self) -> String {
141 self.to_string()
142 }
143}
144
145pub fn result_to_out_err<T, E: ErrorReport>(
151 result: Result<T, E>,
152 out_err: *mut weaveffi_error,
153) -> Option<T> {
154 match result {
155 Ok(value) => {
156 error_set_ok(out_err);
157 Some(value)
158 }
159 Err(e) => {
160 error_set(out_err, e.code(), &e.message());
161 None
162 }
163 }
164}
165
166pub const PANIC_ERROR_CODE: i32 = -2;
173
174pub fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
177 if let Some(s) = payload.downcast_ref::<&str>() {
178 (*s).to_string()
179 } else if let Some(s) = payload.downcast_ref::<String>() {
180 s.clone()
181 } else {
182 "producer panicked".to_string()
183 }
184}
185
186pub fn error_set_panic(out_err: *mut weaveffi_error, payload: &(dyn std::any::Any + Send)) {
190 error_set(
191 out_err,
192 PANIC_ERROR_CODE,
193 &format!("producer panicked: {}", panic_message(payload)),
194 );
195}
196
197#[allow(clippy::missing_panics_doc)]
202pub fn string_to_c_ptr(s: impl AsRef<str>) -> *const c_char {
203 let s = s.as_ref();
204 let sanitized = if s.as_bytes().contains(&0) {
205 s.replace('\0', "")
206 } else {
207 s.to_owned()
208 };
209 let cstr = CString::new(sanitized).expect("string_to_c_ptr: unexpected NUL after sanitization");
210 cstr.into_raw()
211}
212
213pub fn free_string(ptr: *const c_char) {
215 if ptr.is_null() {
216 return;
217 }
218 unsafe { drop(CString::from_raw(ptr as *mut c_char)) };
220}
221
222pub fn free_bytes(ptr: *mut u8, len: usize) {
224 if ptr.is_null() {
225 return;
226 }
227 unsafe { drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len))) };
229}
230
231#[cfg(target_arch = "wasm32")]
236const WASM_ALLOC_ALIGN: usize = 8;
237
238#[cfg(target_arch = "wasm32")]
245pub fn wasm_alloc(size: usize) -> *mut u8 {
246 let size = size.max(1);
247 let layout = std::alloc::Layout::from_size_align(size, WASM_ALLOC_ALIGN)
248 .expect("weaveffi_alloc: invalid layout");
249 unsafe { std::alloc::alloc(layout) }
251}
252
253#[cfg(target_arch = "wasm32")]
257pub fn wasm_dealloc(ptr: *mut u8, size: usize) {
258 if ptr.is_null() {
259 return;
260 }
261 let size = size.max(1);
262 let layout = std::alloc::Layout::from_size_align(size, WASM_ALLOC_ALIGN)
263 .expect("weaveffi_dealloc: invalid layout");
264 unsafe { std::alloc::dealloc(ptr, layout) };
266}
267
268pub fn error_clear(err: *mut weaveffi_error) {
270 error_set_ok(err);
271}
272
273#[repr(C)]
279pub struct weaveffi_cancel_token {
280 cancelled: AtomicBool,
281}
282
283pub fn cancel_token_create() -> *mut weaveffi_cancel_token {
286 Box::into_raw(Box::new(weaveffi_cancel_token {
287 cancelled: AtomicBool::new(false),
288 }))
289}
290
291pub fn cancel_token_cancel(token: *mut weaveffi_cancel_token) {
293 if token.is_null() {
294 return;
295 }
296 let t = unsafe { &*token };
298 t.cancelled.store(true, Ordering::Release);
299}
300
301pub fn cancel_token_is_cancelled(token: *const weaveffi_cancel_token) -> bool {
303 if token.is_null() {
304 return false;
305 }
306 let t = unsafe { &*token };
308 t.cancelled.load(Ordering::Acquire)
309}
310
311pub fn cancel_token_destroy(token: *mut weaveffi_cancel_token) {
313 if token.is_null() {
314 return;
315 }
316 unsafe { drop(Box::from_raw(token)) };
318}
319
320pub struct CancelToken {
338 raw: *const weaveffi_cancel_token,
339}
340
341unsafe impl Send for CancelToken {}
345unsafe impl Sync for CancelToken {}
347
348impl CancelToken {
349 #[doc(hidden)]
355 #[must_use]
356 pub fn from_raw(raw: *const weaveffi_cancel_token) -> Self {
357 Self { raw }
358 }
359
360 #[must_use]
362 pub fn is_cancelled(&self) -> bool {
363 cancel_token_is_cancelled(self.raw)
364 }
365}
366
367pub struct Iter<T> {
376 inner: Box<dyn Iterator<Item = T> + Send>,
377}
378
379impl<T> Iter<T> {
380 pub fn new<I>(iter: I) -> Self
385 where
386 I: IntoIterator<Item = T>,
387 I::IntoIter: Send + 'static,
388 {
389 Self {
390 inner: Box::new(iter.into_iter()),
391 }
392 }
393}
394
395impl<T> Iterator for Iter<T> {
396 type Item = T;
397
398 fn next(&mut self) -> Option<T> {
399 self.inner.next()
400 }
401}
402
403pub fn block_on<F: std::future::Future>(fut: F) -> F::Output {
422 use std::sync::Arc;
423 use std::task::{Context, Poll, Wake, Waker};
424 use std::thread::{self, Thread};
425
426 struct ThreadWaker(Thread);
427 impl Wake for ThreadWaker {
428 fn wake(self: Arc<Self>) {
429 self.0.unpark();
430 }
431 fn wake_by_ref(self: &Arc<Self>) {
432 self.0.unpark();
433 }
434 }
435
436 let mut fut = Box::pin(fut);
437 let waker = Waker::from(Arc::new(ThreadWaker(thread::current())));
438 let mut cx = Context::from_waker(&waker);
439 loop {
440 match fut.as_mut().poll(&mut cx) {
441 Poll::Ready(out) => return out,
442 Poll::Pending => thread::park(),
443 }
444 }
445}
446
447pub fn c_ptr_to_string(ptr: *const c_char) -> Option<String> {
450 if ptr.is_null() {
451 return None;
452 }
453 let c = unsafe { CStr::from_ptr(ptr) };
455 c.to_str().ok().map(|s| s.to_owned())
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn string_roundtrip_and_free() {
464 let ptr = string_to_c_ptr("hello world");
465 assert!(!ptr.is_null());
466 let recovered = c_ptr_to_string(ptr).unwrap();
467 assert_eq!(recovered, "hello world");
468 free_string(ptr);
469 }
470
471 #[test]
472 fn free_string_null_is_safe() {
473 free_string(ptr::null());
474 }
475
476 #[test]
477 fn free_bytes_null_is_safe() {
478 free_bytes(ptr::null_mut(), 0);
479 }
480
481 #[test]
482 fn bytes_alloc_and_free() {
483 let data: Vec<u8> = vec![1, 2, 3, 4, 5];
484 let len = data.len();
485 let boxed = data.into_boxed_slice();
486 let ptr = Box::into_raw(boxed) as *mut u8;
487 free_bytes(ptr, len);
488 }
489
490 #[test]
491 fn error_default_is_ok() {
492 let err = weaveffi_error::default();
493 assert_eq!(err.code, 0);
494 assert!(err.message.is_null());
495 }
496
497 #[test]
498 fn error_set_and_clear() {
499 let mut err = weaveffi_error::default();
500 error_set(&mut err, -1, "something went wrong");
501 assert_eq!(err.code, -1);
502 assert!(!err.message.is_null());
503 let msg = c_ptr_to_string(err.message).unwrap();
504 assert_eq!(msg, "something went wrong");
505 error_clear(&mut err);
506 assert_eq!(err.code, 0);
507 assert!(err.message.is_null());
508 }
509
510 #[test]
511 fn error_clear_null_is_safe() {
512 error_clear(ptr::null_mut());
513 }
514
515 #[test]
516 fn error_set_ok_frees_prior_message() {
517 let mut err = weaveffi_error::default();
518 error_set(&mut err, 1, "first");
519 error_set_ok(&mut err);
520 assert_eq!(err.code, 0);
521 assert!(err.message.is_null());
522 }
523
524 #[test]
525 fn error_set_replaces_prior_message() {
526 let mut err = weaveffi_error::default();
527 error_set(&mut err, 1, "first");
528 error_set(&mut err, 2, "second");
529 assert_eq!(err.code, 2);
530 let msg = c_ptr_to_string(err.message).unwrap();
531 assert_eq!(msg, "second");
532 error_clear(&mut err);
533 }
534
535 #[test]
536 fn result_to_out_err_ok_path() {
537 let mut err = weaveffi_error::default();
538 let val: Result<i32, String> = Ok(42);
539 let opt = result_to_out_err(val, &mut err);
540 assert_eq!(opt, Some(42));
541 assert_eq!(err.code, 0);
542 assert!(err.message.is_null());
543 }
544
545 #[test]
546 fn result_to_out_err_error_path() {
547 let mut err = weaveffi_error::default();
548 let val: Result<i32, String> = Err("bad input".to_string());
549 let opt = result_to_out_err(val, &mut err);
550 assert_eq!(opt, None);
551 assert_eq!(err.code, -1);
552 let msg = c_ptr_to_string(err.message).unwrap();
553 assert_eq!(msg, "bad input");
554 error_clear(&mut err);
555 }
556
557 enum DomainError {
560 NotFound,
561 Io(String),
562 }
563
564 impl ErrorReport for DomainError {
565 fn code(&self) -> i32 {
566 match self {
567 DomainError::NotFound => 1001,
568 DomainError::Io(_) => 1004,
569 }
570 }
571 fn message(&self) -> String {
572 match self {
573 DomainError::NotFound => "not found".to_string(),
574 DomainError::Io(detail) => format!("io: {detail}"),
575 }
576 }
577 }
578
579 #[test]
580 fn error_report_blanket_display_uses_generic_code() {
581 let e = "boom".to_string();
582 assert_eq!(ErrorReport::code(&e), -1);
583 assert_eq!(ErrorReport::message(&e), "boom");
584 }
585
586 #[test]
587 fn error_report_domain_error_carries_its_code() {
588 let mut err = weaveffi_error::default();
589 let val: Result<i32, DomainError> = Err(DomainError::NotFound);
590 assert_eq!(result_to_out_err(val, &mut err), None);
591 assert_eq!(err.code, 1001);
592 assert_eq!(c_ptr_to_string(err.message).unwrap(), "not found");
593 error_clear(&mut err);
594
595 let val: Result<i32, DomainError> = Err(DomainError::Io("disk".to_string()));
596 assert_eq!(result_to_out_err(val, &mut err), None);
597 assert_eq!(err.code, 1004);
598 assert_eq!(c_ptr_to_string(err.message).unwrap(), "io: disk");
599 error_clear(&mut err);
600 }
601
602 #[test]
603 fn string_with_interior_nul_is_sanitized() {
604 let ptr = string_to_c_ptr("hel\0lo");
605 let recovered = c_ptr_to_string(ptr).unwrap();
606 assert_eq!(recovered, "hello");
607 free_string(ptr);
608 }
609
610 #[test]
611 fn c_ptr_to_string_null_returns_none() {
612 assert_eq!(c_ptr_to_string(ptr::null()), None);
613 }
614
615 #[test]
616 fn cancel_token_lifecycle() {
617 let token = cancel_token_create();
618 assert!(!token.is_null());
619 assert!(!cancel_token_is_cancelled(token));
620 cancel_token_cancel(token);
621 assert!(cancel_token_is_cancelled(token));
622 cancel_token_destroy(token);
623 }
624
625 #[test]
626 fn cancel_token_null_is_safe() {
627 cancel_token_cancel(ptr::null_mut());
628 assert!(!cancel_token_is_cancelled(ptr::null()));
629 cancel_token_destroy(ptr::null_mut());
630 }
631}