sz_rust_sdk/ffi/
helpers.rs

1//! Helper functions for FFI operations
2
3use crate::error::{SzError, SzResult};
4use libc::{c_char, 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/// Converts an optional Rust string to a C string pointer
14pub(crate) fn optional_str_to_c_ptr(s: Option<&str>) -> SzResult<*const c_char> {
15    match s {
16        Some(s) => {
17            let c_string = str_to_c_string(s)?;
18            // We need to leak this string to keep it alive for the FFI call
19            Ok(c_string.into_raw() as *const c_char)
20        }
21        None => Ok(ptr::null()),
22    }
23}
24
25/// Converts a C string pointer to a Rust string and frees the C string
26///
27/// # Safety
28///
29/// The caller must ensure that `ptr` is either null or a valid pointer to a null-terminated C string
30/// that was allocated by the Senzing library and can be freed with `Sz_free`.
31pub(crate) unsafe fn c_str_to_string(ptr: *mut c_char) -> SzResult<String> {
32    if ptr.is_null() {
33        return Ok(String::new());
34    }
35
36    let c_str = unsafe { CStr::from_ptr(ptr) };
37    let result = match c_str.to_str() {
38        Ok(s) => Ok(s.to_string()),
39        Err(_) => {
40            // If the C string contains invalid UTF-8, convert it to hex encoding to preserve binary data
41            // This handles cases where Senzing returns binary data or handles
42            let bytes = c_str.to_bytes();
43            Ok(hex::encode(bytes))
44        }
45    };
46
47    // Free the C string memory using Senzing's free function
48    unsafe { super::bindings::Sz_free(ptr) };
49
50    result
51}
52
53/// Converts C string to Rust string without freeing the memory (for static/managed strings)
54///
55/// # Safety
56///
57/// The caller must ensure that ptr is either null or points to a valid
58/// null-terminated C string. This function does NOT free the memory.
59pub(crate) unsafe fn c_str_to_string_no_free(ptr: *mut c_char) -> SzResult<String> {
60    if ptr.is_null() {
61        return Ok(String::new());
62    }
63
64    let c_str = unsafe { CStr::from_ptr(ptr) };
65    match c_str.to_str() {
66        Ok(s) => Ok(s.to_string()),
67        Err(_) => {
68            // If the C string contains invalid UTF-8, convert it to hex encoding to preserve binary data
69            let bytes = c_str.to_bytes();
70            Ok(hex::encode(bytes))
71        }
72    }
73}
74
75/// Processes an SzPointerResult from helper functions
76///
77/// # Safety
78///
79/// The caller must ensure that the SzPointerResult contains valid data
80pub(crate) unsafe fn process_pointer_result(
81    result: super::bindings::SzPointerResult,
82) -> SzResult<String> {
83    if result.return_code != 0 {
84        return Err(SzError::from_code(result.return_code));
85    }
86
87    unsafe { c_str_to_string(result.response) }
88}
89
90/// Processes an SzPointerResult from config helper functions
91///
92/// # Safety
93///
94/// The caller must ensure that the SzPointerResult contains valid data
95/// and that any response pointer is valid and null-terminated.
96pub(crate) unsafe fn process_config_pointer_result(
97    result: super::bindings::SzPointerResult,
98) -> SzResult<String> {
99    if result.return_code != 0 {
100        check_config_return_code(result.return_code)?;
101        unreachable!("check_config_return_code should have returned an error");
102    }
103
104    unsafe { c_str_to_string(result.response) }
105}
106
107/// Processes an SzPointerResult from config helper functions and returns raw bytes
108///
109/// # Safety
110///
111/// The caller must ensure that the SzPointerResult contains valid data
112/// and that any response pointer is valid.
113pub(crate) unsafe fn process_config_pointer_result_bytes(
114    result: super::bindings::SzPointerResult,
115) -> SzResult<Vec<u8>> {
116    if result.return_code != 0 {
117        check_config_return_code(result.return_code)?;
118        unreachable!("check_config_return_code should have returned an error");
119    }
120
121    unsafe { c_str_to_bytes(result.response) }
122}
123
124/// Processes an SzPointerResult from engine helper functions
125///
126/// # Safety
127///
128/// The caller must ensure that the SzPointerResult contains valid data
129/// and that any response pointer is valid and null-terminated.
130pub(crate) unsafe fn process_engine_pointer_result(
131    result: super::bindings::SzPointerResult,
132) -> SzResult<String> {
133    if result.return_code != 0 {
134        check_return_code(result.return_code)?;
135        unreachable!("check_return_code should have returned an error");
136    }
137
138    unsafe { c_str_to_string(result.response) }
139}
140
141/// Converts C string to raw bytes for handle storage
142///
143/// # Safety
144///
145/// The caller must ensure that ptr is either null or points to a valid
146/// null-terminated C string that can be safely freed with Sz_free.
147pub(crate) unsafe fn c_str_to_bytes(ptr: *mut c_char) -> SzResult<Vec<u8>> {
148    if ptr.is_null() {
149        return Ok(Vec::new());
150    }
151
152    let c_str = unsafe { CStr::from_ptr(ptr) };
153    let bytes = c_str.to_bytes().to_vec();
154
155    // Free the C string memory using Senzing's free function
156    unsafe { super::bindings::Sz_free(ptr) };
157
158    Ok(bytes)
159}
160
161/// Processes an SzPointerResult from config manager helper functions
162///
163/// # Safety
164///
165/// The caller must ensure that the SzPointerResult contains valid data
166/// and that any response pointer is valid and null-terminated.
167pub(crate) unsafe fn process_config_mgr_pointer_result(
168    result: super::bindings::SzPointerResult,
169) -> SzResult<String> {
170    if result.return_code != 0 {
171        check_config_mgr_return_code(result.return_code)?;
172        unreachable!("check_config_mgr_return_code should have returned an error");
173    }
174
175    unsafe { c_str_to_string(result.response) }
176}
177
178/// Processes an SzLongResult from config manager helper functions
179pub(crate) fn process_config_mgr_long_result(
180    result: super::bindings::SzLongResult,
181) -> SzResult<i64> {
182    if result.return_code != 0 {
183        check_config_mgr_return_code(result.return_code)?;
184        unreachable!("check_config_mgr_return_code should have returned an error");
185    }
186
187    Ok(result.response)
188}
189
190/// Processes an SzLongResult from helper functions
191pub(crate) fn process_long_result(result: super::bindings::SzLongResult) -> SzResult<i64> {
192    if result.return_code != 0 {
193        return Err(SzError::from_code(result.return_code));
194    }
195
196    Ok(result.response)
197}
198
199/// Handles memory allocation for FFI response strings
200pub(crate) struct ResponseBuffer {
201    pub ptr: *mut c_char,
202    pub size: size_t,
203}
204
205impl Default for ResponseBuffer {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211impl ResponseBuffer {
212    pub(crate) fn new() -> Self {
213        Self {
214            ptr: ptr::null_mut(),
215            size: 0,
216        }
217    }
218
219    pub(crate) fn as_string(&self) -> SzResult<String> {
220        unsafe { c_str_to_string(self.ptr) }
221    }
222}
223
224impl Drop for ResponseBuffer {
225    fn drop(&mut self) {
226        if !self.ptr.is_null() {
227            unsafe {
228                super::bindings::Sz_free(self.ptr);
229            }
230        }
231    }
232}
233
234/// Checks the return code from Senzing FFI functions
235pub(crate) fn check_return_code(return_code: i64) -> SzResult<()> {
236    if return_code == 0 {
237        Ok(())
238    } else {
239        let mut buffer: Vec<u8> = vec![0; 1024];
240        let buffer_len = buffer.len() as i64;
241
242        let error_msg = unsafe {
243            let actual_len =
244                super::bindings::Sz_getLastException(buffer.as_mut_ptr() as *mut i8, buffer_len);
245
246            if actual_len > 0 && actual_len < buffer_len {
247                // Sz_getLastException returns length including null terminator
248                // Exclude the null terminator from the string content
249                let string_len = if actual_len > 0 {
250                    (actual_len as usize) - 1
251                } else {
252                    0
253                };
254                buffer.truncate(string_len);
255                String::from_utf8_lossy(&buffer).to_string()
256            } else {
257                format!("Unknown error (code: {})", return_code)
258            }
259        };
260
261        // Create error with proper Senzing message - backtrace is now captured automatically
262        match return_code {
263            -1 => Err(SzError::unknown(error_msg)),
264            -2 => Err(SzError::configuration(error_msg)),
265            -3 => Err(SzError::bad_input(error_msg)),
266            -4 => Err(SzError::retryable(error_msg)),
267            -5 => Err(SzError::unrecoverable(error_msg)),
268            -6 => Err(SzError::not_found(error_msg)),
269            -7 => Err(SzError::license(error_msg)),
270            -8 => Err(SzError::database(error_msg)),
271            _ => Err(SzError::unknown(error_msg)),
272        }
273    }
274}
275
276/// Checks the return code from Senzing Config FFI functions
277pub(crate) fn check_config_return_code(return_code: i64) -> SzResult<()> {
278    if return_code == 0 {
279        Ok(())
280    } else {
281        let mut buffer: Vec<u8> = vec![0; 1024];
282        let buffer_len = buffer.len() as i64;
283
284        let error_msg = unsafe {
285            let actual_len = super::bindings::SzConfig_getLastException(
286                buffer.as_mut_ptr() as *mut i8,
287                buffer_len,
288            );
289
290            if actual_len > 0 && actual_len < buffer_len {
291                let string_len = if actual_len > 0 {
292                    (actual_len as usize) - 1
293                } else {
294                    0
295                };
296                buffer.truncate(string_len);
297                String::from_utf8_lossy(&buffer).to_string()
298            } else {
299                format!("Unknown Config error (code: {})", return_code)
300            }
301        };
302
303        // Create error with proper Senzing message - backtrace is captured at error creation
304        match return_code {
305            -1 => Err(SzError::unknown(error_msg)),
306            -2 => Err(SzError::configuration(error_msg)),
307            -3 => Err(SzError::bad_input(error_msg)),
308            -4 => Err(SzError::retryable(error_msg)),
309            -5 => Err(SzError::unrecoverable(error_msg)),
310            -6 => Err(SzError::not_found(error_msg)),
311            -7 => Err(SzError::license(error_msg)),
312            -8 => Err(SzError::database(error_msg)),
313            _ => Err(SzError::unknown(error_msg)),
314        }
315    }
316}
317
318/// Checks the return code from Senzing ConfigMgr FFI functions
319pub(crate) fn check_config_mgr_return_code(return_code: i64) -> SzResult<()> {
320    if return_code == 0 {
321        Ok(())
322    } else {
323        let mut buffer: Vec<u8> = vec![0; 1024];
324        let buffer_len = buffer.len() as i64;
325
326        let error_msg = unsafe {
327            let actual_len = super::bindings::SzConfigMgr_getLastException(
328                buffer.as_mut_ptr() as *mut i8,
329                buffer_len,
330            );
331
332            if actual_len > 0 && actual_len < buffer_len {
333                let string_len = if actual_len > 0 {
334                    (actual_len as usize) - 1
335                } else {
336                    0
337                };
338                buffer.truncate(string_len);
339                String::from_utf8_lossy(&buffer).to_string()
340            } else {
341                format!("Unknown ConfigMgr error (code: {})", return_code)
342            }
343        };
344
345        // Create error with proper Senzing message - backtrace is now captured automatically
346        match return_code {
347            -1 => Err(SzError::unknown(error_msg)),
348            -2 => Err(SzError::configuration(error_msg)),
349            -3 => Err(SzError::bad_input(error_msg)),
350            -4 => Err(SzError::retryable(error_msg)),
351            -5 => Err(SzError::unrecoverable(error_msg)),
352            -6 => Err(SzError::not_found(error_msg)),
353            -7 => Err(SzError::license(error_msg)),
354            -8 => Err(SzError::database(error_msg)),
355            _ => Err(SzError::unknown(error_msg)),
356        }
357    }
358}
359
360/// Checks the return code from SzProduct FFI functions
361pub(crate) fn check_product_return_code(return_code: i64) -> SzResult<()> {
362    if return_code == 0 {
363        Ok(())
364    } else {
365        let mut buffer: Vec<u8> = vec![0; 1024];
366        let buffer_len = buffer.len() as i64;
367
368        let error_msg = unsafe {
369            let actual_len = super::bindings::SzProduct_getLastException(
370                buffer.as_mut_ptr() as *mut i8,
371                buffer_len,
372            );
373
374            if actual_len > 0 && actual_len < buffer_len {
375                let string_len = if actual_len > 0 {
376                    (actual_len as usize) - 1
377                } else {
378                    0
379                };
380                buffer.truncate(string_len);
381                String::from_utf8_lossy(&buffer).to_string()
382            } else {
383                format!("Unknown error (code: {})", return_code)
384            }
385        };
386
387        // Create error with proper Senzing message
388        match return_code {
389            -1 => Err(SzError::unknown(error_msg)),
390            -2 => Err(SzError::configuration(error_msg)),
391            -3 => Err(SzError::bad_input(error_msg)),
392            -4 => Err(SzError::retryable(error_msg)),
393            -5 => Err(SzError::unrecoverable(error_msg)),
394            -6 => Err(SzError::not_found(error_msg)),
395            -7 => Err(SzError::license(error_msg)),
396            -8 => Err(SzError::database(error_msg)),
397            _ => Err(SzError::unknown(error_msg)),
398        }
399    }
400}
401
402/// Checks the return code from Senzing FFI functions (deprecated alias)
403#[deprecated(note = "Use check_return_code directly")]
404pub(crate) fn check_return_code_i64(return_code: i64) -> SzResult<()> {
405    check_return_code(return_code)
406}
407
408/// Macro for safely calling FFI functions with proper error handling
409#[doc(hidden)]
410#[macro_export]
411macro_rules! ffi_call {
412    ($ffi_fn:expr) => {{
413        #[allow(clippy::macro_metavars_in_unsafe)]
414        let result = unsafe { $ffi_fn };
415        $crate::ffi::helpers::check_return_code(result)?;
416    }};
417}
418
419/// Macro for safely calling Config FFI functions with proper error handling
420#[doc(hidden)]
421#[macro_export]
422macro_rules! ffi_call_config {
423    ($ffi_fn:expr) => {{
424        #[allow(clippy::macro_metavars_in_unsafe)]
425        let result = unsafe { $ffi_fn };
426        $crate::ffi::helpers::check_config_return_code(result)?;
427    }};
428}
429
430/// Macro for safely calling ConfigMgr FFI functions with proper error handling
431#[doc(hidden)]
432#[macro_export]
433macro_rules! ffi_call_config_mgr {
434    ($ffi_fn:expr) => {{
435        #[allow(clippy::macro_metavars_in_unsafe)]
436        let result = unsafe { $ffi_fn };
437        $crate::ffi::helpers::check_config_mgr_return_code(result)?;
438    }};
439}
440
441/// Macro for safely calling Product FFI functions with proper error handling
442#[doc(hidden)]
443#[macro_export]
444macro_rules! ffi_call_product {
445    ($ffi_fn:expr) => {{
446        #[allow(clippy::macro_metavars_in_unsafe)]
447        let result = unsafe { $ffi_fn };
448        $crate::ffi::helpers::check_product_return_code(result)?;
449    }};
450}
451
452/// Macro for safely calling FFI functions that return i64
453#[doc(hidden)]
454#[macro_export]
455macro_rules! ffi_call_i64 {
456    ($ffi_fn:expr) => {{
457        #[allow(clippy::macro_metavars_in_unsafe)]
458        let result = unsafe { $ffi_fn };
459        $crate::ffi::helpers::check_return_code(result)?;
460    }};
461}
462
463/// Macro for FFI calls that return response data
464#[doc(hidden)]
465#[macro_export]
466macro_rules! ffi_call_with_response {
467    ($ffi_fn:expr) => {{
468        let mut response = $crate::ffi::helpers::ResponseBuffer::new();
469        let result = unsafe { $ffi_fn(&mut response.ptr, &mut response.size) };
470        $crate::ffi::helpers::check_return_code(result)?;
471        response.as_string()
472    }};
473}