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
88impl core::fmt::Debug for BorrowedStr<'_> {
89 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90 let s = unsafe { self.as_str() };
94 write!(f, "BorrowedStr({s:?})")
95 }
96}
97
98#[repr(C)]
103#[derive(Clone, Copy)]
104pub struct Slice<'a, T> {
105 pub ptr: *const T,
106 pub len: usize,
107 _phantom: PhantomData<&'a [T]>,
108}
109
110unsafe impl<T: Sync> Send for Slice<'_, T> {}
112unsafe impl<T: Sync> Sync for Slice<'_, T> {}
114
115impl<'a, T> Slice<'a, T> {
116 #[must_use]
118 pub const fn empty() -> Self {
119 Self {
120 ptr: ptr::null(),
121 len: 0,
122 _phantom: PhantomData,
123 }
124 }
125
126 #[must_use]
128 pub const fn from_slice(s: &'a [T]) -> Self {
129 Self {
130 ptr: s.as_ptr(),
131 len: s.len(),
132 _phantom: PhantomData,
133 }
134 }
135
136 #[must_use]
142 pub unsafe fn as_slice(&self) -> &'a [T] {
143 if self.ptr.is_null() || self.len == 0 {
144 return &[];
145 }
146 unsafe { slice::from_raw_parts(self.ptr, self.len) }
148 }
149}
150
151#[repr(u32)]
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum PluginErrorCode {
157 Ok = 0,
158 Generic = 1,
159 Panic = 2,
160 InvalidArgument = 3,
161 NotImplemented = 4,
162 AbiMismatch = 5,
163 SerializationFailed = 6,
164}
165
166#[repr(C)]
173pub struct OwnedBytes {
174 pub ptr: *mut u8,
175 pub len: usize,
176 pub cap: usize,
177 pub drop_fn: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize, cap: usize)>,
178}
179
180unsafe impl Send for OwnedBytes {}
183
184impl OwnedBytes {
185 #[must_use]
187 pub const fn empty() -> Self {
188 Self {
189 ptr: ptr::null_mut(),
190 len: 0,
191 cap: 0,
192 drop_fn: None,
193 }
194 }
195
196 #[must_use]
198 pub fn is_empty(&self) -> bool {
199 self.len == 0 || self.ptr.is_null()
200 }
201
202 #[must_use]
213 pub fn from_vec(v: Vec<u8>) -> Self {
214 let mut v = core::mem::ManuallyDrop::new(v);
215 let ptr = v.as_mut_ptr();
216 let len = v.len();
217 let cap = v.capacity();
218 Self {
219 ptr,
220 len,
221 cap,
222 drop_fn: Some(drop_owned_bytes),
223 }
224 }
225
226 #[must_use]
232 pub unsafe fn as_bytes(&self) -> &[u8] {
233 if self.is_empty() {
234 return &[];
235 }
236 unsafe { slice::from_raw_parts(self.ptr, self.len) }
238 }
239}
240
241impl Drop for OwnedBytes {
242 fn drop(&mut self) {
243 if let Some(f) = self.drop_fn.take()
244 && !self.ptr.is_null()
245 {
246 unsafe { f(self.ptr, self.len, self.cap) };
249 self.ptr = ptr::null_mut();
250 self.len = 0;
251 self.cap = 0;
252 }
253 }
254}
255
256pub unsafe extern "C" fn drop_owned_bytes(ptr: *mut u8, len: usize, cap: usize) {
264 if ptr.is_null() {
265 return;
266 }
267 unsafe {
269 let _ = Vec::from_raw_parts(ptr, len, cap);
270 }
271}
272
273#[repr(C)]
278pub struct PluginError {
279 pub code: PluginErrorCode,
280 pub message: OwnedBytes,
281}
282
283impl PluginError {
284 #[must_use]
286 pub fn generic(message: impl AsRef<str>) -> Self {
287 Self {
288 code: PluginErrorCode::Generic,
289 message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
290 }
291 }
292
293 #[must_use]
295 pub fn new(code: PluginErrorCode, message: impl AsRef<str>) -> Self {
296 Self {
297 code,
298 message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
299 }
300 }
301
302 #[must_use]
304 pub fn panic(message: impl AsRef<str>) -> Self {
305 Self::new(PluginErrorCode::Panic, message)
306 }
307
308 #[must_use]
310 pub fn message_string(&self) -> String {
311 let bytes = unsafe { self.message.as_bytes() };
313 String::from_utf8_lossy(bytes).into_owned()
314 }
315}
316
317impl core::fmt::Debug for PluginError {
318 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
319 f.debug_struct(stringify!(PluginError))
320 .field("code", &self.code)
321 .field("message", &self.message_string())
322 .finish()
323 }
324}
325
326#[repr(C, u8)]
331pub enum PluginResult<T> {
332 Ok(T),
333 Err(PluginError),
334}
335
336impl<T> PluginResult<T> {
337 pub fn into_result(self) -> Result<T, PluginError> {
339 match self {
340 Self::Ok(t) => Ok(t),
341 Self::Err(e) => Err(e),
342 }
343 }
344
345 pub fn from_result(r: Result<T, PluginError>) -> Self {
347 match r {
348 Ok(t) => Self::Ok(t),
349 Err(e) => Self::Err(e),
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use std::sync::atomic::{AtomicUsize, Ordering};
357
358 use rstest::rstest;
359
360 use super::*;
361
362 #[rstest]
363 #[case::ascii("hello")]
364 #[case::empty("")]
365 #[case::utf8("héllo wörld")]
366 #[case::multibyte("\u{1F600}\u{1F4A9}")]
367 fn borrowed_str_round_trips(#[case] s: &str) {
368 let b = BorrowedStr::from_str(s);
369 let back = unsafe { b.as_str() };
371 assert_eq!(back, s);
372 }
373
374 #[rstest]
375 fn slice_round_trips() {
376 let data: [u32; 3] = [1, 2, 3];
377 let s = Slice::from_slice(&data);
378 let back = unsafe { s.as_slice() };
380 assert_eq!(back, &[1u32, 2, 3]);
381 }
382
383 #[rstest]
384 fn empty_slice_returns_empty() {
385 let s: Slice<u8> = Slice::empty();
386 let back = unsafe { s.as_slice() };
388 assert!(back.is_empty());
389 }
390
391 #[rstest]
392 fn owned_bytes_round_trip_and_drop() {
393 let payload = b"hello world".to_vec();
394 let owned = OwnedBytes::from_vec(payload.clone());
395 let view = unsafe { owned.as_bytes() }.to_vec();
397 assert_eq!(view, payload);
398 drop(owned);
399 }
400
401 #[rstest]
402 fn owned_bytes_drop_fn_runs_exactly_once() {
403 static COUNTER: AtomicUsize = AtomicUsize::new(0);
404 unsafe extern "C" fn counting_drop(ptr: *mut u8, len: usize, cap: usize) {
405 if !ptr.is_null() {
406 COUNTER.fetch_add(1, Ordering::SeqCst);
407 unsafe {
409 let _ = Vec::from_raw_parts(ptr, len, cap);
410 }
411 }
412 }
413
414 COUNTER.store(0, Ordering::SeqCst);
415 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4]);
416 let ptr = v.as_mut_ptr();
417 let len = v.len();
418 let cap = v.capacity();
419 let owned = OwnedBytes {
420 ptr,
421 len,
422 cap,
423 drop_fn: Some(counting_drop),
424 };
425 assert_eq!(COUNTER.load(Ordering::SeqCst), 0);
426 drop(owned);
427 assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
428 }
429
430 #[rstest]
431 fn plugin_error_carries_message() {
432 let err = PluginError::generic("bad input");
433 assert_eq!(err.code, PluginErrorCode::Generic);
434 assert_eq!(err.message_string(), "bad input");
435 }
436
437 #[rstest]
438 fn plugin_result_round_trips() {
439 let ok: PluginResult<u32> = PluginResult::Ok(42);
440 let r = ok.into_result();
441 assert_eq!(r.unwrap(), 42);
442
443 let err: PluginResult<u32> = PluginResult::Err(PluginError::generic("nope"));
444 let r = err.into_result();
445 assert!(r.is_err());
446 }
447
448 #[rstest]
449 fn plugin_result_from_result_round_trips() {
450 let r: PluginResult<u32> = PluginResult::from_result(Ok(7));
451 assert_eq!(r.into_result().unwrap(), 7);
452
453 let r: PluginResult<u32> = PluginResult::from_result(Err(PluginError::generic("x")));
454 let e = r.into_result().unwrap_err();
455 assert_eq!(e.code, PluginErrorCode::Generic);
456 assert_eq!(e.message_string(), "x");
457 }
458
459 #[rstest]
460 fn borrowed_str_empty_is_empty_when_borrowed_back() {
461 let b = BorrowedStr::empty();
462 assert!(b.ptr.is_null());
463 assert_eq!(b.len, 0);
464 assert_eq!(unsafe { b.as_str() }, "");
466 }
467
468 #[rstest]
469 fn borrowed_str_debug_prints_contents() {
470 let b = BorrowedStr::from_str("hello");
471 let rendered = format!("{b:?}");
472 assert!(rendered.contains("hello"));
473 }
474
475 #[rstest]
476 fn slice_empty_descriptor_is_null_and_zero_len() {
477 let s: Slice<u32> = Slice::empty();
478 assert!(s.ptr.is_null());
479 assert_eq!(s.len, 0);
480 }
481
482 #[rstest]
483 fn owned_bytes_empty_is_empty() {
484 let owned = OwnedBytes::empty();
485 assert!(owned.is_empty());
486 assert!(owned.ptr.is_null());
487 assert_eq!(owned.len, 0);
488 assert_eq!(owned.cap, 0);
489 assert!(owned.drop_fn.is_none());
490 assert!(unsafe { owned.as_bytes() }.is_empty());
492 }
493
494 #[rstest]
495 fn owned_bytes_is_empty_for_zero_length_buffer() {
496 let owned = OwnedBytes::from_vec(Vec::new());
497 assert!(owned.is_empty());
498 drop(owned);
499 }
500
501 #[rstest]
502 fn owned_bytes_drop_with_null_ptr_short_circuits() {
503 let owned = OwnedBytes {
504 ptr: ptr::null_mut(),
505 len: 0,
506 cap: 0,
507 drop_fn: Some(drop_owned_bytes),
508 };
509 drop(owned);
511 }
512
513 #[rstest]
514 fn drop_owned_bytes_handles_null_ptr_without_panic() {
515 unsafe {
517 drop_owned_bytes(ptr::null_mut(), 0, 0);
518 };
519 }
520
521 #[rstest]
522 fn drop_owned_bytes_frees_vec_leaked_with_from_vec_layout() {
523 let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4, 5]);
524 let ptr = v.as_mut_ptr();
525 let len = v.len();
526 let cap = v.capacity();
527 unsafe {
531 drop_owned_bytes(ptr, len, cap);
532 };
533 }
534
535 #[rstest]
536 fn plugin_error_new_carries_code_and_message() {
537 let err = PluginError::new(PluginErrorCode::InvalidArgument, "bad arg");
538 assert_eq!(err.code, PluginErrorCode::InvalidArgument);
539 assert_eq!(err.message_string(), "bad arg");
540 }
541
542 #[rstest]
543 fn plugin_error_panic_sets_panic_code() {
544 let err = PluginError::panic("oops");
545 assert_eq!(err.code, PluginErrorCode::Panic);
546 assert_eq!(err.message_string(), "oops");
547 }
548
549 #[rstest]
550 fn plugin_error_debug_renders_code_and_message() {
551 let err = PluginError::new(PluginErrorCode::NotImplemented, "todo");
552 let rendered = format!("{err:?}");
553 assert!(rendered.contains("NotImplemented"));
554 assert!(rendered.contains("todo"));
555 }
556
557 #[rstest]
558 #[case::ok(PluginErrorCode::Ok, 0u32)]
559 #[case::generic(PluginErrorCode::Generic, 1)]
560 #[case::panic(PluginErrorCode::Panic, 2)]
561 #[case::invalid_argument(PluginErrorCode::InvalidArgument, 3)]
562 #[case::not_implemented(PluginErrorCode::NotImplemented, 4)]
563 #[case::abi_mismatch(PluginErrorCode::AbiMismatch, 5)]
564 #[case::serialization_failed(PluginErrorCode::SerializationFailed, 6)]
565 fn plugin_error_code_has_stable_discriminant(
566 #[case] code: PluginErrorCode,
567 #[case] expected: u32,
568 ) {
569 assert_eq!(code as u32, expected);
570 }
571}