Skip to main content

ffi_bridge/
types.rs

1//! # types — Shared FFI-safe type aliases and helpers
2//!
3//! This module re-exports the primitive FFI types and defines higher-level
4//! type aliases and conversion utilities used throughout the crate.
5
6use crate::errors::FfiError;
7use crate::memory::FfiBuffer;
8
9// ─── Type aliases ─────────────────────────────────────────────────────────────
10
11/// A fallible FFI operation: either produces an [`FfiBuffer`] or an [`FfiError`].
12pub type FfiBufferResult = Result<FfiBuffer, FfiError>;
13
14// ─── BridgeValue ──────────────────────────────────────────────────────────────
15
16/// A strongly-typed wrap around `FfiBuffer` that carries a JSON-serialized value.
17///
18/// Provides idiomatic Rust serialization/deserialization helpers while
19/// remaining FFI-safe when consumed as a raw `FfiBuffer`.
20pub struct BridgeValue {
21    inner: FfiBuffer,
22}
23
24impl BridgeValue {
25    /// Serialize a value to JSON and wrap it.
26    pub fn new<T: serde::Serialize>(value: &T) -> Result<Self, FfiError> {
27        Ok(BridgeValue {
28            inner: FfiBuffer::from_json(value)?,
29        })
30    }
31
32    /// Deserialize the contained JSON into `T`.
33    pub fn decode<T: serde::de::DeserializeOwned>(&self) -> Result<T, FfiError> {
34        unsafe { self.inner.to_json() }
35    }
36
37    /// Consume the `BridgeValue` and return the underlying buffer.
38    pub fn into_buffer(self) -> FfiBuffer {
39        self.inner
40    }
41}
42
43// ─── Utility functions ────────────────────────────────────────────────────────
44
45/// Validate that a raw pointer is non-null, returning [`FfiError::NullPointer`] if it is.
46///
47/// # Safety
48///
49/// The pointer must be valid for reads if it is non-null.
50#[inline]
51pub unsafe fn check_not_null<T>(ptr: *const T) -> Result<*const T, FfiError> {
52    if ptr.is_null() {
53        Err(FfiError::NullPointer)
54    } else {
55        Ok(ptr)
56    }
57}
58
59/// Validate that a mutable pointer is non-null, returning [`FfiError::NullPointer`] if it is.
60///
61/// # Safety
62///
63/// The pointer must be valid for writes if it is non-null.
64#[inline]
65pub unsafe fn check_not_null_mut<T>(ptr: *mut T) -> Result<*mut T, FfiError> {
66    if ptr.is_null() {
67        Err(FfiError::NullPointer)
68    } else {
69        Ok(ptr)
70    }
71}
72
73/// Read a C-string pointer into an owned `String`.
74///
75/// # Safety
76///
77/// `ptr` must point to a valid null-terminated UTF-8 sequence.
78pub unsafe fn cstr_to_string(ptr: *const std::os::raw::c_char) -> Result<String, FfiError> {
79    if ptr.is_null() {
80        return Err(FfiError::NullPointer);
81    }
82    std::ffi::CStr::from_ptr(ptr)
83        .to_str()
84        .map(|s| s.to_string())
85        .map_err(|e| FfiError::InvalidUtf8(e.to_string()))
86}
87
88// ─── Tests ────────────────────────────────────────────────────────────────────
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
95    struct Point {
96        x: i32,
97        y: i32,
98    }
99
100    #[test]
101    fn bridge_value_round_trip() {
102        let p = Point { x: 10, y: -5 };
103        let bv = BridgeValue::new(&p).unwrap();
104        let decoded: Point = bv.decode().unwrap();
105        assert_eq!(decoded, p);
106        // Manually free the inner buffer since BridgeValue has no Drop
107        crate::memory::ffi_buffer_free(bv.into_buffer());
108    }
109
110    #[test]
111    fn check_not_null_with_null_ptr() {
112        let ptr: *const u8 = std::ptr::null();
113        let result = unsafe { check_not_null(ptr) };
114        assert!(matches!(result, Err(FfiError::NullPointer)));
115    }
116
117    #[test]
118    fn check_not_null_with_valid_ptr() {
119        let value: u8 = 42;
120        let ptr: *const u8 = &value;
121        let result = unsafe { check_not_null(ptr) };
122        assert!(result.is_ok());
123    }
124}