use core::ffi::{c_char, c_int, c_long};
use std::{cell::Cell, ffi::CString, marker::PhantomData, sync::MutexGuard};
use super::CoolPropError;
use crate::io::GlobalParam;
type PhantomUnsync = PhantomData<Cell<()>>;
#[derive(Debug)]
pub(crate) struct ErrorBuffer {
code: c_long,
pub message: StringBuffer,
marker: PhantomUnsync,
}
impl ErrorBuffer {
pub fn blank() -> Self {
Self { code: 0, message: StringBuffer::blank(), marker: PhantomData }
}
#[must_use]
pub fn code_as_mut_ptr(&mut self) -> *mut c_long {
&raw mut self.code
}
#[must_use]
#[allow(dead_code)]
pub fn code(&self) -> c_long {
self.code
}
}
impl Default for ErrorBuffer {
fn default() -> Self {
Self { code: 0, message: StringBuffer::default(), marker: PhantomData }
}
}
impl From<ErrorBuffer> for Option<CoolPropError> {
fn from(value: ErrorBuffer) -> Self {
value.message.into()
}
}
#[derive(Debug)]
pub(crate) struct StringBuffer {
capacity: usize,
buffer: *mut c_char,
marker: PhantomUnsync,
}
impl StringBuffer {
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
if capacity == 0 {
return Self { capacity, buffer: std::ptr::null_mut(), marker: PhantomData };
}
let vec = vec![0u8; capacity];
let buffer = unsafe { CString::from_vec_unchecked(vec) }.into_raw();
Self { capacity, buffer, marker: PhantomData }
}
#[must_use]
pub fn blank() -> Self {
Self::with_capacity(0)
}
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut c_char {
self.buffer
}
#[must_use]
pub fn capacity(&self) -> c_int {
self.capacity as c_int
}
}
impl Default for StringBuffer {
fn default() -> Self {
Self::with_capacity(500)
}
}
impl Drop for StringBuffer {
fn drop(&mut self) {
if !self.buffer.is_null() {
unsafe {
drop(CString::from_raw(self.buffer));
}
}
}
}
impl From<StringBuffer> for String {
fn from(value: StringBuffer) -> Self {
if value.buffer.is_null() {
return Self::new();
}
let buffer = value.buffer;
std::mem::forget(value);
let c_string = unsafe { CString::from_raw(buffer) };
c_string.into_string().unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned())
}
}
impl From<StringBuffer> for Option<CoolPropError> {
fn from(value: StringBuffer) -> Self {
let message: String = value.into();
if message.trim().is_empty() { None } else { Some(CoolPropError(message)) }
}
}
pub(crate) fn get_error(
lock: &MutexGuard<coolprop_sys::bindings::CoolProp>,
) -> Option<CoolPropError> {
let mut message = StringBuffer::default();
let param = CString::new(GlobalParam::PendingError.as_ref()).unwrap();
let _unused = unsafe {
lock.get_global_param_string(param.as_ptr(), message.as_mut_ptr(), message.capacity())
};
message.into()
}
#[cfg(test)]
mod tests {
use rstest::*;
use super::*;
mod error_buffer {
use super::*;
#[test]
fn blank() {
let sut = ErrorBuffer::blank();
assert_eq!(sut.code(), 0);
assert_eq!(sut.message.capacity(), 0);
}
#[test]
fn default() {
let sut = ErrorBuffer::default();
assert_eq!(sut.code(), 0);
assert_eq!(sut.message.capacity(), 500);
}
#[test]
fn code_as_mut_ptr() {
let mut sut = ErrorBuffer::default();
unsafe {
*sut.code_as_mut_ptr() = 42;
}
assert_eq!(sut.code(), 42);
}
#[rstest]
#[case("", None)]
#[case(" ", None)]
#[case("Error message", Some(CoolPropError("Error message".into())))]
fn into_coolprop_error(#[case] msg: &str, #[case] expected: Option<CoolPropError>) {
let mut sut = ErrorBuffer::default();
let c_string = CString::new(msg).unwrap();
let c_bytes = c_string.as_bytes_with_nul();
unsafe {
std::ptr::copy_nonoverlapping(
c_bytes.as_ptr().cast::<c_char>(),
sut.message.as_mut_ptr(),
c_bytes.len(),
);
}
let res: Option<CoolPropError> = sut.into();
assert_eq!(res, expected);
}
}
mod string_buffer {
use super::*;
#[rstest]
#[case(0)]
#[case(42)]
fn with_capacity(#[case] capacity: usize) {
let sut = StringBuffer::with_capacity(capacity);
assert_eq!(sut.capacity(), capacity as c_int);
}
#[test]
fn blank() {
let sut = StringBuffer::blank();
assert_eq!(sut.capacity(), 0);
}
#[test]
fn default() {
let sut = StringBuffer::default();
assert_eq!(sut.capacity(), 500);
}
#[rstest]
#[case("")]
#[case("something")]
#[case(" something else ")]
fn into_string(#[case] value: &str) {
let c_string = CString::new(value).unwrap();
let c_bytes = c_string.as_bytes_with_nul();
let mut sut = StringBuffer::with_capacity(c_bytes.len());
unsafe {
std::ptr::copy_nonoverlapping(
c_bytes.as_ptr().cast::<c_char>(),
sut.as_mut_ptr(),
c_bytes.len(),
);
}
let res: String = sut.into();
assert_eq!(res, value);
}
#[test]
fn into_string_empty() {
let sut = StringBuffer::with_capacity(42);
let res: String = sut.into();
assert!(res.is_empty());
}
#[test]
fn into_string_blank() {
let sut = StringBuffer::blank();
let res: String = sut.into();
assert!(res.is_empty());
}
#[test]
fn into_string_lossy() {
let invalid_utf8: Vec<u8> = vec![
b'H', b'e', b'l', b'l', b'o', 0xFF, 0xFE, b'W', b'o', b'r', b'l', b'd', b'!', b'\0',
];
let mut sut = StringBuffer::with_capacity(invalid_utf8.len());
unsafe {
std::ptr::copy_nonoverlapping(
invalid_utf8.as_ptr().cast::<c_char>(),
sut.as_mut_ptr(),
invalid_utf8.len(),
);
}
let res: String = sut.into();
assert!(res.contains('\u{FFFD}')); assert_eq!(res, "Hello\u{FFFD}\u{FFFD}World!");
}
#[rstest]
#[case("", None)]
#[case(" ", None)]
#[case("Error message", Some(CoolPropError("Error message".into())))]
fn into_coolprop_error(#[case] value: &str, #[case] expected: Option<CoolPropError>) {
let c_string = CString::new(value).unwrap();
let c_bytes = c_string.as_bytes_with_nul();
let mut sut = StringBuffer::with_capacity(c_bytes.len());
unsafe {
std::ptr::copy_nonoverlapping(
c_bytes.as_ptr().cast::<c_char>(),
sut.as_mut_ptr(),
c_bytes.len(),
);
}
let res: Option<CoolPropError> = sut.into();
assert_eq!(res, expected);
}
}
}