1#![allow(unsafe_code)]
25
26use core::{marker::PhantomData, ptr, slice};
27
28#[repr(C)]
35#[derive(Clone, Copy)]
36pub struct BorrowedStr<'a> {
37 pub ptr: *const u8,
38 pub len: usize,
39 _phantom: PhantomData<&'a [u8]>,
40}
41
42unsafe impl Send for BorrowedStr<'_> {}
46unsafe impl Sync for BorrowedStr<'_> {}
48
49impl<'a> BorrowedStr<'a> {
50 #[must_use]
52 pub const fn empty() -> Self {
53 Self {
54 ptr: ptr::null(),
55 len: 0,
56 _phantom: PhantomData,
57 }
58 }
59
60 #[must_use]
62 pub const fn from_str(s: &'a str) -> Self {
63 Self {
64 ptr: s.as_ptr(),
65 len: s.len(),
66 _phantom: PhantomData,
67 }
68 }
69
70 #[must_use]
77 pub unsafe fn as_str(&self) -> &'a str {
78 if self.ptr.is_null() || self.len == 0 {
79 return "";
80 }
81 let bytes = unsafe { slice::from_raw_parts(self.ptr, self.len) };
83 unsafe { core::str::from_utf8_unchecked(bytes) }
85 }
86
87 pub unsafe fn try_as_str(&self) -> Result<&'a str, core::str::Utf8Error> {
100 if self.ptr.is_null() || self.len == 0 {
101 return Ok("");
102 }
103 let bytes = unsafe { slice::from_raw_parts(self.ptr, self.len) };
105 core::str::from_utf8(bytes)
106 }
107
108 #[must_use]
115 pub unsafe fn to_string_lossy(&self) -> String {
116 if self.ptr.is_null() || self.len == 0 {
117 return String::new();
118 }
119 let bytes = unsafe { slice::from_raw_parts(self.ptr, self.len) };
121 String::from_utf8_lossy(bytes).into_owned()
122 }
123}
124
125impl core::fmt::Debug for BorrowedStr<'_> {
126 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
127 let s = unsafe { self.to_string_lossy() };
132 write!(f, "BorrowedStr({s:?})")
133 }
134}
135
136#[repr(C)]
141#[derive(Clone, Copy)]
142pub struct Slice<'a, T> {
143 pub ptr: *const T,
144 pub len: usize,
145 _phantom: PhantomData<&'a [T]>,
146}
147
148unsafe impl<T: Sync> Send for Slice<'_, T> {}
150unsafe impl<T: Sync> Sync for Slice<'_, T> {}
152
153impl<'a, T> Slice<'a, T> {
154 #[must_use]
156 pub const fn empty() -> Self {
157 Self {
158 ptr: ptr::null(),
159 len: 0,
160 _phantom: PhantomData,
161 }
162 }
163
164 #[must_use]
166 pub const fn from_slice(s: &'a [T]) -> Self {
167 Self {
168 ptr: s.as_ptr(),
169 len: s.len(),
170 _phantom: PhantomData,
171 }
172 }
173
174 #[must_use]
180 pub unsafe fn as_slice(&self) -> &'a [T] {
181 if self.ptr.is_null() || self.len == 0 {
182 return &[];
183 }
184 unsafe { slice::from_raw_parts(self.ptr, self.len) }
186 }
187}
188
189#[repr(u32)]
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
194pub enum PluginErrorCode {
195 Ok = 0,
196 Generic = 1,
197 Panic = 2,
198 InvalidArgument = 3,
199 NotImplemented = 4,
200 AbiMismatch = 5,
201 SerializationFailed = 6,
202}
203
204#[repr(C)]
211pub struct OwnedBytes {
212 pub ptr: *mut u8,
213 pub len: usize,
214 pub cap: usize,
215 pub drop_fn: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize, cap: usize)>,
216}
217
218unsafe impl Send for OwnedBytes {}
221
222impl OwnedBytes {
223 #[must_use]
225 pub const fn empty() -> Self {
226 Self {
227 ptr: ptr::null_mut(),
228 len: 0,
229 cap: 0,
230 drop_fn: None,
231 }
232 }
233
234 #[must_use]
236 pub fn is_empty(&self) -> bool {
237 self.len == 0 || self.ptr.is_null()
238 }
239
240 #[must_use]
251 pub fn from_vec(v: Vec<u8>) -> Self {
252 let mut v = core::mem::ManuallyDrop::new(v);
253 let ptr = v.as_mut_ptr();
254 let len = v.len();
255 let cap = v.capacity();
256 Self {
257 ptr,
258 len,
259 cap,
260 drop_fn: Some(drop_owned_bytes),
261 }
262 }
263
264 #[must_use]
270 pub unsafe fn as_bytes(&self) -> &[u8] {
271 if self.is_empty() {
272 return &[];
273 }
274 unsafe { slice::from_raw_parts(self.ptr, self.len) }
276 }
277}
278
279impl Drop for OwnedBytes {
280 fn drop(&mut self) {
281 if let Some(f) = self.drop_fn.take()
282 && !self.ptr.is_null()
283 {
284 unsafe { f(self.ptr, self.len, self.cap) };
287 self.ptr = ptr::null_mut();
288 self.len = 0;
289 self.cap = 0;
290 }
291 }
292}
293
294pub unsafe extern "C" fn drop_owned_bytes(ptr: *mut u8, len: usize, cap: usize) {
302 if ptr.is_null() {
303 return;
304 }
305 unsafe {
307 let _ = Vec::from_raw_parts(ptr, len, cap);
308 }
309}
310
311#[repr(C)]
316pub struct PluginError {
317 pub code: PluginErrorCode,
318 pub message: OwnedBytes,
319}
320
321impl PluginError {
322 #[must_use]
324 pub fn generic(message: impl AsRef<str>) -> Self {
325 Self {
326 code: PluginErrorCode::Generic,
327 message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
328 }
329 }
330
331 #[must_use]
333 pub fn new(code: PluginErrorCode, message: impl AsRef<str>) -> Self {
334 Self {
335 code,
336 message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
337 }
338 }
339
340 #[must_use]
342 pub fn panic(message: impl AsRef<str>) -> Self {
343 Self::new(PluginErrorCode::Panic, message)
344 }
345
346 #[must_use]
348 pub fn message_string(&self) -> String {
349 let bytes = unsafe { self.message.as_bytes() };
351 String::from_utf8_lossy(bytes).into_owned()
352 }
353}
354
355impl core::fmt::Debug for PluginError {
356 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
357 f.debug_struct(stringify!(PluginError))
358 .field("code", &self.code)
359 .field("message", &self.message_string())
360 .finish()
361 }
362}
363
364#[repr(C, u8)]
369pub enum PluginResult<T> {
370 Ok(T),
371 Err(PluginError),
372}
373
374impl<T> PluginResult<T> {
375 pub fn into_result(self) -> Result<T, PluginError> {
381 match self {
382 Self::Ok(t) => Ok(t),
383 Self::Err(e) => Err(e),
384 }
385 }
386
387 pub fn from_result(r: Result<T, PluginError>) -> Self {
389 match r {
390 Ok(t) => Self::Ok(t),
391 Err(e) => Self::Err(e),
392 }
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use std::sync::atomic::{AtomicUsize, Ordering};
399
400 use rstest::rstest;
401
402 use super::*;
403
404 #[rstest]
405 #[case::ascii("hello")]
406 #[case::empty("")]
407 #[case::utf8("héllo wörld")]
408 #[case::multibyte("\u{1F600}\u{1F4A9}")]
409 fn borrowed_str_round_trips(#[case] s: &str) {
410 let b = BorrowedStr::from_str(s);
411 let back = unsafe { b.as_str() };
413 assert_eq!(back, s);
414 }
415
416 #[rstest]
417 fn borrowed_str_try_as_str_rejects_invalid_utf8() {
418 static INVALID_UTF8: [u8; 1] = [0xFF];
419 let mut b = BorrowedStr::empty();
420 b.ptr = INVALID_UTF8.as_ptr();
421 b.len = INVALID_UTF8.len();
422
423 let result = unsafe { b.try_as_str() };
425
426 assert!(result.is_err());
427 }
428
429 #[rstest]
430 fn borrowed_str_to_string_lossy_replaces_invalid_utf8() {
431 static INVALID_UTF8: [u8; 1] = [0xFF];
432 let mut b = BorrowedStr::empty();
433 b.ptr = INVALID_UTF8.as_ptr();
434 b.len = INVALID_UTF8.len();
435
436 let rendered = unsafe { b.to_string_lossy() };
438
439 assert_eq!(rendered, "\u{FFFD}");
440 }
441
442 #[rstest]
443 fn slice_round_trips() {
444 let data: [u32; 3] = [1, 2, 3];
445 let s = Slice::from_slice(&data);
446 let back = unsafe { s.as_slice() };
448 assert_eq!(back, &[1u32, 2, 3]);
449 }
450
451 #[rstest]
452 fn empty_slice_returns_empty() {
453 let s: Slice<u8> = Slice::empty();
454 let back = unsafe { s.as_slice() };
456 assert!(back.is_empty());
457 }
458
459 #[rstest]
460 fn owned_bytes_round_trip_and_drop() {
461 let payload = b"hello world".to_vec();
462 let owned = OwnedBytes::from_vec(payload.clone());
463 let view = unsafe { owned.as_bytes() }.to_vec();
465 assert_eq!(view, payload);
466 drop(owned);
467 }
468
469 #[rstest]
470 fn owned_bytes_drop_fn_runs_exactly_once() {
471 static COUNTER: AtomicUsize = AtomicUsize::new(0);
472 unsafe extern "C" fn counting_drop(ptr: *mut u8, len: usize, cap: usize) {
473 if !ptr.is_null() {
474 COUNTER.fetch_add(1, Ordering::SeqCst);
475 unsafe {
477 let _ = Vec::from_raw_parts(ptr, len, cap);
478 }
479 }
480 }
481
482 COUNTER.store(0, Ordering::SeqCst);
483 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4]);
484 let ptr = v.as_mut_ptr();
485 let len = v.len();
486 let cap = v.capacity();
487 let owned = OwnedBytes {
488 ptr,
489 len,
490 cap,
491 drop_fn: Some(counting_drop),
492 };
493 assert_eq!(COUNTER.load(Ordering::SeqCst), 0);
494 drop(owned);
495 assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
496 }
497
498 #[rstest]
499 fn plugin_error_carries_message() {
500 let err = PluginError::generic("bad input");
501 assert_eq!(err.code, PluginErrorCode::Generic);
502 assert_eq!(err.message_string(), "bad input");
503 }
504
505 #[rstest]
506 fn plugin_result_round_trips() {
507 let ok: PluginResult<u32> = PluginResult::Ok(42);
508 let r = ok.into_result();
509 assert_eq!(r.unwrap(), 42);
510
511 let err: PluginResult<u32> = PluginResult::Err(PluginError::generic("nope"));
512 let r = err.into_result();
513 assert!(r.is_err());
514 }
515
516 #[rstest]
517 fn plugin_result_from_result_round_trips() {
518 let r: PluginResult<u32> = PluginResult::from_result(Ok(7));
519 assert_eq!(r.into_result().unwrap(), 7);
520
521 let r: PluginResult<u32> = PluginResult::from_result(Err(PluginError::generic("x")));
522 let e = r.into_result().unwrap_err();
523 assert_eq!(e.code, PluginErrorCode::Generic);
524 assert_eq!(e.message_string(), "x");
525 }
526
527 #[rstest]
528 fn borrowed_str_empty_is_empty_when_borrowed_back() {
529 let b = BorrowedStr::empty();
530 assert!(b.ptr.is_null());
531 assert_eq!(b.len, 0);
532 assert_eq!(unsafe { b.as_str() }, "");
534 }
535
536 #[rstest]
537 fn borrowed_str_debug_prints_contents() {
538 let b = BorrowedStr::from_str("hello");
539 let rendered = format!("{b:?}");
540 assert!(rendered.contains("hello"));
541 }
542
543 #[rstest]
544 fn slice_empty_descriptor_is_null_and_zero_len() {
545 let s: Slice<u32> = Slice::empty();
546 assert!(s.ptr.is_null());
547 assert_eq!(s.len, 0);
548 }
549
550 #[rstest]
551 fn owned_bytes_empty_is_empty() {
552 let owned = OwnedBytes::empty();
553 assert!(owned.is_empty());
554 assert!(owned.ptr.is_null());
555 assert_eq!(owned.len, 0);
556 assert_eq!(owned.cap, 0);
557 assert!(owned.drop_fn.is_none());
558 assert!(unsafe { owned.as_bytes() }.is_empty());
560 }
561
562 #[rstest]
563 fn owned_bytes_is_empty_for_zero_length_buffer() {
564 let owned = OwnedBytes::from_vec(Vec::new());
565 assert!(owned.is_empty());
566 drop(owned);
567 }
568
569 #[rstest]
570 fn owned_bytes_drop_with_null_ptr_short_circuits() {
571 let owned = OwnedBytes {
572 ptr: ptr::null_mut(),
573 len: 0,
574 cap: 0,
575 drop_fn: Some(drop_owned_bytes),
576 };
577 drop(owned);
579 }
580
581 #[rstest]
582 fn drop_owned_bytes_handles_null_ptr_without_panic() {
583 unsafe {
585 drop_owned_bytes(ptr::null_mut(), 0, 0);
586 };
587 }
588
589 #[rstest]
590 fn drop_owned_bytes_frees_vec_leaked_with_from_vec_layout() {
591 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4, 5]);
592 let ptr = v.as_mut_ptr();
593 let len = v.len();
594 let cap = v.capacity();
595 unsafe {
599 drop_owned_bytes(ptr, len, cap);
600 };
601 }
602
603 #[rstest]
604 fn plugin_error_new_carries_code_and_message() {
605 let err = PluginError::new(PluginErrorCode::InvalidArgument, "bad arg");
606 assert_eq!(err.code, PluginErrorCode::InvalidArgument);
607 assert_eq!(err.message_string(), "bad arg");
608 }
609
610 #[rstest]
611 fn plugin_error_panic_sets_panic_code() {
612 let err = PluginError::panic("oops");
613 assert_eq!(err.code, PluginErrorCode::Panic);
614 assert_eq!(err.message_string(), "oops");
615 }
616
617 #[rstest]
618 fn plugin_error_debug_renders_code_and_message() {
619 let err = PluginError::new(PluginErrorCode::NotImplemented, "todo");
620 let rendered = format!("{err:?}");
621 assert!(rendered.contains("NotImplemented"));
622 assert!(rendered.contains("todo"));
623 }
624
625 #[rstest]
626 #[case::ok(PluginErrorCode::Ok, 0u32)]
627 #[case::generic(PluginErrorCode::Generic, 1)]
628 #[case::panic(PluginErrorCode::Panic, 2)]
629 #[case::invalid_argument(PluginErrorCode::InvalidArgument, 3)]
630 #[case::not_implemented(PluginErrorCode::NotImplemented, 4)]
631 #[case::abi_mismatch(PluginErrorCode::AbiMismatch, 5)]
632 #[case::serialization_failed(PluginErrorCode::SerializationFailed, 6)]
633 fn plugin_error_code_has_stable_discriminant(
634 #[case] code: PluginErrorCode,
635 #[case] expected: u32,
636 ) {
637 assert_eq!(code as u32, expected);
638 }
639}