sz_rust_sdk/ffi/
helpers.rs

1//! Helper functions for FFI operations
2
3use crate::error::{SzError, SzResult};
4use libc::{c_char, c_void, size_t};
5use std::ffi::{CStr, CString};
6use std::ptr;
7
8/// Converts a Rust string to a C string (Internal)
9pub(crate) fn str_to_c_string(s: &str) -> SzResult<CString> {
10    CString::new(s).map_err(SzError::from)
11}
12
13/// Frees memory allocated by Senzing helper functions
14///
15/// # Safety
16/// ptr must be a valid pointer allocated by Senzing or null
17#[inline]
18pub(crate) unsafe fn sz_free(ptr: *mut c_char) {
19    if !ptr.is_null() {
20        unsafe { super::SzHelper_free(ptr as *mut c_void) };
21    }
22}
23
24/// Converts a C string pointer to a Rust string and frees the C string
25///
26/// # Safety
27///
28/// The caller must ensure that `ptr` is either null or a valid pointer to a null-terminated C string
29/// that was allocated by the Senzing library.
30pub(crate) unsafe fn c_str_to_string(ptr: *mut c_char) -> SzResult<String> {
31    if ptr.is_null() {
32        return Ok(String::new());
33    }
34
35    let c_str = unsafe { CStr::from_ptr(ptr) };
36    let result = match c_str.to_str() {
37        Ok(s) => Ok(s.to_string()),
38        Err(_) => {
39            // If the C string contains invalid UTF-8, convert it to hex encoding to preserve binary data
40            let bytes = c_str.to_bytes();
41            Ok(hex::encode(bytes))
42        }
43    };
44
45    // Free the C string memory using Senzing's free function
46    unsafe { sz_free(ptr) };
47
48    result
49}
50
51/// Converts C string to Rust string without freeing the memory (for static/managed strings)
52///
53/// # Safety
54///
55/// The caller must ensure that ptr is either null or points to a valid
56/// null-terminated C string. This function does NOT free the memory.
57pub(crate) unsafe fn c_str_to_string_no_free(ptr: *mut c_char) -> SzResult<String> {
58    if ptr.is_null() {
59        return Ok(String::new());
60    }
61
62    let c_str = unsafe { CStr::from_ptr(ptr) };
63    match c_str.to_str() {
64        Ok(s) => Ok(s.to_string()),
65        Err(_) => {
66            let bytes = c_str.to_bytes();
67            Ok(hex::encode(bytes))
68        }
69    }
70}
71
72/// Converts C string to raw bytes for handle storage
73///
74/// # Safety
75///
76/// The caller must ensure that ptr is either null or points to a valid
77/// null-terminated C string that can be safely freed.
78pub(crate) unsafe fn c_str_to_bytes(ptr: *mut c_char) -> SzResult<Vec<u8>> {
79    if ptr.is_null() {
80        return Ok(Vec::new());
81    }
82
83    let c_str = unsafe { CStr::from_ptr(ptr) };
84    let bytes = c_str.to_bytes().to_vec();
85
86    unsafe { sz_free(ptr) };
87
88    Ok(bytes)
89}
90
91/// Handles memory allocation for FFI response strings
92pub(crate) struct ResponseBuffer {
93    pub ptr: *mut c_char,
94    pub size: size_t,
95}
96
97impl Default for ResponseBuffer {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl ResponseBuffer {
104    pub(crate) fn new() -> Self {
105        Self {
106            ptr: ptr::null_mut(),
107            size: 0,
108        }
109    }
110
111    pub(crate) fn as_string(&self) -> SzResult<String> {
112        unsafe { c_str_to_string(self.ptr) }
113    }
114}
115
116impl Drop for ResponseBuffer {
117    fn drop(&mut self) {
118        if !self.ptr.is_null() {
119            unsafe {
120                sz_free(self.ptr);
121            }
122        }
123    }
124}
125
126/// Checks the return code from Senzing Engine FFI functions
127pub(crate) fn check_return_code(return_code: i64) -> SzResult<()> {
128    if return_code == 0 {
129        return Ok(());
130    }
131
132    let actual_error_code = unsafe { super::Sz_getLastExceptionCode() };
133    Err(SzError::from_code_with_message(
134        actual_error_code,
135        crate::error::SzComponent::Engine,
136    ))
137}
138
139/// Checks the return code from Senzing Config FFI functions
140pub(crate) fn check_config_return_code(return_code: i64) -> SzResult<()> {
141    if return_code == 0 {
142        return Ok(());
143    }
144
145    let actual_error_code = unsafe { super::SzConfig_getLastExceptionCode() };
146    Err(SzError::from_code_with_message(
147        actual_error_code,
148        crate::error::SzComponent::Config,
149    ))
150}
151
152/// Checks the return code from Senzing ConfigMgr FFI functions
153pub(crate) fn check_config_mgr_return_code(return_code: i64) -> SzResult<()> {
154    if return_code == 0 {
155        return Ok(());
156    }
157
158    let actual_error_code = unsafe { super::SzConfigMgr_getLastExceptionCode() };
159    Err(SzError::from_code_with_message(
160        actual_error_code,
161        crate::error::SzComponent::ConfigMgr,
162    ))
163}
164
165/// Checks the return code from SzProduct FFI functions
166pub(crate) fn check_product_return_code(return_code: i64) -> SzResult<()> {
167    if return_code == 0 {
168        return Ok(());
169    }
170
171    let actual_error_code = unsafe { super::SzProduct_getLastExceptionCode() };
172    Err(SzError::from_code_with_message(
173        actual_error_code,
174        crate::error::SzComponent::Product,
175    ))
176}
177
178/// Checks the return code from SzDiagnostic FFI functions
179pub(crate) fn check_diagnostic_return_code(return_code: i64) -> SzResult<()> {
180    if return_code == 0 {
181        return Ok(());
182    }
183
184    let actual_error_code = unsafe { super::SzDiagnostic_getLastExceptionCode() };
185    Err(SzError::from_code_with_message(
186        actual_error_code,
187        crate::error::SzComponent::Diagnostic,
188    ))
189}
190
191/// Macro for safely calling FFI functions with proper error handling
192#[doc(hidden)]
193#[macro_export]
194macro_rules! ffi_call {
195    ($ffi_fn:expr) => {{
196        #[allow(clippy::macro_metavars_in_unsafe)]
197        let result = unsafe { $ffi_fn };
198        $crate::ffi::helpers::check_return_code(result)?;
199    }};
200}
201
202/// Macro for safely calling Config FFI functions with proper error handling
203#[doc(hidden)]
204#[macro_export]
205macro_rules! ffi_call_config {
206    ($ffi_fn:expr) => {{
207        #[allow(clippy::macro_metavars_in_unsafe)]
208        let result = unsafe { $ffi_fn };
209        $crate::ffi::helpers::check_config_return_code(result)?;
210    }};
211}
212
213/// Macro for safely calling ConfigMgr FFI functions with proper error handling
214#[doc(hidden)]
215#[macro_export]
216macro_rules! ffi_call_config_mgr {
217    ($ffi_fn:expr) => {{
218        #[allow(clippy::macro_metavars_in_unsafe)]
219        let result = unsafe { $ffi_fn };
220        $crate::ffi::helpers::check_config_mgr_return_code(result)?;
221    }};
222}
223
224/// Macro for safely calling Product FFI functions with proper error handling
225#[doc(hidden)]
226#[macro_export]
227macro_rules! ffi_call_product {
228    ($ffi_fn:expr) => {{
229        #[allow(clippy::macro_metavars_in_unsafe)]
230        let result = unsafe { $ffi_fn };
231        $crate::ffi::helpers::check_product_return_code(result)?;
232    }};
233}
234
235/// Macro for safely calling Diagnostic FFI functions with proper error handling
236#[doc(hidden)]
237#[macro_export]
238macro_rules! ffi_call_diagnostic {
239    ($ffi_fn:expr) => {{
240        #[allow(clippy::macro_metavars_in_unsafe)]
241        let result = unsafe { $ffi_fn };
242        $crate::ffi::helpers::check_diagnostic_return_code(result)?;
243    }};
244}
245
246/// Macro for safely calling FFI functions that return i64
247#[doc(hidden)]
248#[macro_export]
249macro_rules! ffi_call_i64 {
250    ($ffi_fn:expr) => {{
251        #[allow(clippy::macro_metavars_in_unsafe)]
252        let result = unsafe { $ffi_fn };
253        $crate::ffi::helpers::check_return_code(result)?;
254    }};
255}
256
257/// Macro for FFI calls that return response data
258#[doc(hidden)]
259#[macro_export]
260macro_rules! ffi_call_with_response {
261    ($ffi_fn:expr) => {{
262        let mut response = $crate::ffi::helpers::ResponseBuffer::new();
263        let result = unsafe { $ffi_fn(&mut response.ptr, &mut response.size) };
264        $crate::ffi::helpers::check_return_code(result)?;
265        response.as_string()
266    }};
267}
268
269/// Macro to process helper function results that have response and returnCode fields
270/// Works with any bindgen-generated result type
271#[doc(hidden)]
272#[macro_export]
273macro_rules! process_result {
274    ($result:expr, $check_fn:path) => {{
275        $check_fn($result.returnCode)?;
276        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
277    }};
278}
279
280/// Process engine helper result
281#[doc(hidden)]
282#[macro_export]
283macro_rules! process_engine_result {
284    ($result:expr) => {{
285        $crate::ffi::helpers::check_return_code($result.returnCode)?;
286        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
287    }};
288}
289
290/// Process config helper result
291#[doc(hidden)]
292#[macro_export]
293macro_rules! process_config_result {
294    ($result:expr) => {{
295        $crate::ffi::helpers::check_config_return_code($result.returnCode)?;
296        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
297    }};
298}
299
300/// Process config manager helper result (string response)
301#[doc(hidden)]
302#[macro_export]
303macro_rules! process_config_mgr_result {
304    ($result:expr) => {{
305        $crate::ffi::helpers::check_config_mgr_return_code($result.returnCode)?;
306        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
307    }};
308}
309
310/// Process config manager helper result (i64 response like configID)
311#[doc(hidden)]
312#[macro_export]
313macro_rules! process_config_mgr_long_result {
314    ($result:expr) => {{
315        $crate::ffi::helpers::check_config_mgr_return_code($result.returnCode)?;
316        Ok($result.configID)
317    }};
318}
319
320/// Process diagnostic helper result
321#[doc(hidden)]
322#[macro_export]
323macro_rules! process_diagnostic_result {
324    ($result:expr) => {{
325        $crate::ffi::helpers::check_diagnostic_return_code($result.returnCode)?;
326        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
327    }};
328}
329
330/// Process product helper result
331#[doc(hidden)]
332#[macro_export]
333macro_rules! process_product_result {
334    ($result:expr) => {{
335        $crate::ffi::helpers::check_product_return_code($result.returnCode)?;
336        unsafe { $crate::ffi::helpers::c_str_to_string($result.response) }
337    }};
338}