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 slice_round_trips() {
418 let data: [u32; 3] = [1, 2, 3];
419 let s = Slice::from_slice(&data);
420 let back = unsafe { s.as_slice() };
422 assert_eq!(back, &[1u32, 2, 3]);
423 }
424
425 #[rstest]
426 fn empty_slice_returns_empty() {
427 let s: Slice<u8> = Slice::empty();
428 let back = unsafe { s.as_slice() };
430 assert!(back.is_empty());
431 }
432
433 #[rstest]
434 fn owned_bytes_round_trip_and_drop() {
435 let payload = b"hello world".to_vec();
436 let owned = OwnedBytes::from_vec(payload.clone());
437 let view = unsafe { owned.as_bytes() }.to_vec();
439 assert_eq!(view, payload);
440 drop(owned);
441 }
442
443 #[rstest]
444 fn owned_bytes_drop_fn_runs_exactly_once() {
445 static COUNTER: AtomicUsize = AtomicUsize::new(0);
446 unsafe extern "C" fn counting_drop(ptr: *mut u8, len: usize, cap: usize) {
447 if !ptr.is_null() {
448 COUNTER.fetch_add(1, Ordering::SeqCst);
449 unsafe {
451 let _ = Vec::from_raw_parts(ptr, len, cap);
452 }
453 }
454 }
455
456 COUNTER.store(0, Ordering::SeqCst);
457 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4]);
458 let ptr = v.as_mut_ptr();
459 let len = v.len();
460 let cap = v.capacity();
461 let owned = OwnedBytes {
462 ptr,
463 len,
464 cap,
465 drop_fn: Some(counting_drop),
466 };
467 assert_eq!(COUNTER.load(Ordering::SeqCst), 0);
468 drop(owned);
469 assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
470 }
471
472 #[rstest]
473 fn plugin_error_carries_message() {
474 let err = PluginError::generic("bad input");
475 assert_eq!(err.code, PluginErrorCode::Generic);
476 assert_eq!(err.message_string(), "bad input");
477 }
478
479 #[rstest]
480 fn plugin_result_round_trips() {
481 let ok: PluginResult<u32> = PluginResult::Ok(42);
482 let r = ok.into_result();
483 assert_eq!(r.unwrap(), 42);
484
485 let err: PluginResult<u32> = PluginResult::Err(PluginError::generic("nope"));
486 let r = err.into_result();
487 assert!(r.is_err());
488 }
489
490 #[rstest]
491 fn plugin_result_from_result_round_trips() {
492 let r: PluginResult<u32> = PluginResult::from_result(Ok(7));
493 assert_eq!(r.into_result().unwrap(), 7);
494
495 let r: PluginResult<u32> = PluginResult::from_result(Err(PluginError::generic("x")));
496 let e = r.into_result().unwrap_err();
497 assert_eq!(e.code, PluginErrorCode::Generic);
498 assert_eq!(e.message_string(), "x");
499 }
500
501 #[rstest]
502 fn borrowed_str_empty_is_empty_when_borrowed_back() {
503 let b = BorrowedStr::empty();
504 assert!(b.ptr.is_null());
505 assert_eq!(b.len, 0);
506 assert_eq!(unsafe { b.as_str() }, "");
508 }
509
510 #[rstest]
511 fn borrowed_str_debug_prints_contents() {
512 let b = BorrowedStr::from_str("hello");
513 let rendered = format!("{b:?}");
514 assert!(rendered.contains("hello"));
515 }
516
517 #[rstest]
518 fn slice_empty_descriptor_is_null_and_zero_len() {
519 let s: Slice<u32> = Slice::empty();
520 assert!(s.ptr.is_null());
521 assert_eq!(s.len, 0);
522 }
523
524 #[rstest]
525 fn owned_bytes_empty_is_empty() {
526 let owned = OwnedBytes::empty();
527 assert!(owned.is_empty());
528 assert!(owned.ptr.is_null());
529 assert_eq!(owned.len, 0);
530 assert_eq!(owned.cap, 0);
531 assert!(owned.drop_fn.is_none());
532 assert!(unsafe { owned.as_bytes() }.is_empty());
534 }
535
536 #[rstest]
537 fn owned_bytes_is_empty_for_zero_length_buffer() {
538 let owned = OwnedBytes::from_vec(Vec::new());
539 assert!(owned.is_empty());
540 drop(owned);
541 }
542
543 #[rstest]
544 fn owned_bytes_drop_with_null_ptr_short_circuits() {
545 let owned = OwnedBytes {
546 ptr: ptr::null_mut(),
547 len: 0,
548 cap: 0,
549 drop_fn: Some(drop_owned_bytes),
550 };
551 drop(owned);
553 }
554
555 #[rstest]
556 fn drop_owned_bytes_handles_null_ptr_without_panic() {
557 unsafe {
559 drop_owned_bytes(ptr::null_mut(), 0, 0);
560 };
561 }
562
563 #[rstest]
564 fn drop_owned_bytes_frees_vec_leaked_with_from_vec_layout() {
565 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4, 5]);
566 let ptr = v.as_mut_ptr();
567 let len = v.len();
568 let cap = v.capacity();
569 unsafe {
573 drop_owned_bytes(ptr, len, cap);
574 };
575 }
576
577 #[rstest]
578 fn plugin_error_new_carries_code_and_message() {
579 let err = PluginError::new(PluginErrorCode::InvalidArgument, "bad arg");
580 assert_eq!(err.code, PluginErrorCode::InvalidArgument);
581 assert_eq!(err.message_string(), "bad arg");
582 }
583
584 #[rstest]
585 fn plugin_error_panic_sets_panic_code() {
586 let err = PluginError::panic("oops");
587 assert_eq!(err.code, PluginErrorCode::Panic);
588 assert_eq!(err.message_string(), "oops");
589 }
590
591 #[rstest]
592 fn plugin_error_debug_renders_code_and_message() {
593 let err = PluginError::new(PluginErrorCode::NotImplemented, "todo");
594 let rendered = format!("{err:?}");
595 assert!(rendered.contains("NotImplemented"));
596 assert!(rendered.contains("todo"));
597 }
598
599 #[rstest]
600 #[case::ok(PluginErrorCode::Ok, 0u32)]
601 #[case::generic(PluginErrorCode::Generic, 1)]
602 #[case::panic(PluginErrorCode::Panic, 2)]
603 #[case::invalid_argument(PluginErrorCode::InvalidArgument, 3)]
604 #[case::not_implemented(PluginErrorCode::NotImplemented, 4)]
605 #[case::abi_mismatch(PluginErrorCode::AbiMismatch, 5)]
606 #[case::serialization_failed(PluginErrorCode::SerializationFailed, 6)]
607 fn plugin_error_code_has_stable_discriminant(
608 #[case] code: PluginErrorCode,
609 #[case] expected: u32,
610 ) {
611 assert_eq!(code as u32, expected);
612 }
613}