use std::{
ffi::{CStr, CString, c_char},
str,
};
#[cfg(feature = "python")]
use pyo3::{Bound, Python, ffi};
use ustr::Ustr;
use crate::ffi::abort_on_panic;
#[cfg(feature = "python")]
#[must_use]
pub unsafe fn pystr_to_string(ptr: *mut ffi::PyObject) -> String {
assert!(!ptr.is_null(), "`ptr` was NULL");
Python::attach(|py| unsafe { Bound::from_borrowed_ptr(py, ptr).to_string() })
}
#[must_use]
pub unsafe fn cstr_to_ustr(ptr: *const c_char) -> Ustr {
assert!(!ptr.is_null(), "`ptr` was NULL");
let cstr = unsafe { CStr::from_ptr(ptr) };
Ustr::from(cstr.to_str().expect("CStr::from_ptr failed"))
}
#[must_use]
pub unsafe fn cstr_to_bytes<'a>(ptr: *const c_char) -> &'a [u8] {
assert!(!ptr.is_null(), "`ptr` was NULL");
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_bytes()
}
#[must_use]
pub unsafe fn optional_cstr_to_ustr(ptr: *const c_char) -> Option<Ustr> {
if ptr.is_null() {
None
} else {
Some(unsafe { cstr_to_ustr(ptr) })
}
}
#[must_use]
pub unsafe fn cstr_as_str<'a>(ptr: *const c_char) -> &'a str {
assert!(!ptr.is_null(), "`ptr` was NULL");
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().expect("C string contains invalid UTF-8")
}
#[must_use]
pub unsafe fn optional_cstr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
if ptr.is_null() {
None
} else {
Some(unsafe { cstr_as_str(ptr) })
}
}
#[must_use]
pub fn str_to_cstr(s: &str) -> *const c_char {
CString::new(s).expect("CString::new failed").into_raw()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cstr_drop(ptr: *const c_char) {
abort_on_panic(|| {
assert!(!ptr.is_null(), "`ptr` was NULL");
let cstring = unsafe { CString::from_raw(ptr.cast_mut()) };
drop(cstring);
});
}
#[cfg(test)]
mod tests {
#[cfg(feature = "python")]
use pyo3::types::PyString;
use rstest::*;
use super::*;
#[cfg(feature = "python")]
#[cfg_attr(miri, ignore)]
#[rstest]
fn test_pystr_to_string() {
Python::initialize();
let ptr = Python::attach(|py| PyString::new(py, "test string1").as_ptr());
let result = unsafe { pystr_to_string(ptr) };
assert_eq!(result, "test string1");
}
#[cfg(feature = "python")]
#[rstest]
#[should_panic(expected = "`ptr` was NULL")]
fn test_pystr_to_string_with_null_ptr() {
let ptr: *mut ffi::PyObject = std::ptr::null_mut();
unsafe {
let _ = pystr_to_string(ptr);
};
}
#[rstest]
fn test_cstr_as_str() {
let c_string = CString::new("test string2").expect("CString::new failed");
let ptr = c_string.as_ptr();
let result = unsafe { cstr_as_str(ptr) };
assert_eq!(result, "test string2");
}
#[rstest]
fn test_cstr_to_bytes() {
let sample_c_string = CString::new("Hello, world!").expect("CString::new failed");
let cstr_ptr = sample_c_string.as_ptr();
let result = unsafe { cstr_to_bytes(cstr_ptr) };
assert_eq!(result, b"Hello, world!");
assert_eq!(result.len(), 13);
}
#[rstest]
#[should_panic(expected = "`ptr` was NULL")]
fn test_cstr_to_bytes_with_null_ptr() {
let ptr: *const c_char = std::ptr::null();
unsafe {
let _ = cstr_to_bytes(ptr);
};
}
#[rstest]
fn test_optional_cstr_to_str_with_null_ptr() {
let ptr = std::ptr::null();
let result = unsafe { optional_cstr_to_str(ptr) };
assert!(result.is_none());
}
#[rstest]
fn test_optional_cstr_to_str_with_valid_ptr() {
let input_str = "hello world";
let c_str = CString::new(input_str).expect("CString::new failed");
let result = unsafe { optional_cstr_to_str(c_str.as_ptr()) };
assert!(result.is_some());
assert_eq!(result.unwrap(), input_str);
}
#[rstest]
fn test_string_to_cstr() {
let s = "test string";
let c_str_ptr = str_to_cstr(s);
let c_str = unsafe { CStr::from_ptr(c_str_ptr) };
let result = c_str.to_str().expect("CStr::from_ptr failed");
assert_eq!(result, s);
}
#[rstest]
fn test_cstr_drop() {
let c_string = CString::new("test string3").expect("CString::new failed");
let ptr = c_string.into_raw(); unsafe { cstr_drop(ptr) };
}
}