use crate::error::{RrdError, RrdResult};
use itertools::Itertools;
use rrd_sys::rrd_char;
use std::{ffi::CString, fmt, path::Path, ptr};
pub fn path_to_str(path: &Path) -> RrdResult<&str> {
path.to_str().ok_or(RrdError::PathEncodingError)
}
pub(crate) struct MaybeNullTerminatedArrayOfStrings<const IS_NULL_TERMINATED: bool> {
cstrings: Vec<CString>,
pointers: Vec<*const rrd_char>,
}
impl<const IS_NULL_TERMINATED: bool> MaybeNullTerminatedArrayOfStrings<IS_NULL_TERMINATED> {
#[cfg(test)]
fn new<T, U>(strings: T) -> RrdResult<Self>
where
T: IntoIterator<Item = U>,
U: Into<String>,
{
strings
.into_iter()
.map(|s| CString::new(s.into()))
.collect::<Result<Self, _>>()
.map_err(|e| e.into())
}
pub fn as_ptr(&self) -> *mut *const rrd_char {
if self.is_empty() {
ptr::null_mut()
} else {
self.pointers.as_ptr().cast_mut()
}
}
pub fn len(&self) -> usize {
self.cstrings.len()
}
pub fn is_empty(&self) -> bool {
self.cstrings.is_empty()
}
}
impl<const T: bool> fmt::Debug for MaybeNullTerminatedArrayOfStrings<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.cstrings.iter()).finish()
}
}
impl<const IS_NULL_TERMINATED: bool> FromIterator<CString>
for MaybeNullTerminatedArrayOfStrings<IS_NULL_TERMINATED>
{
fn from_iter<T: IntoIterator<Item = CString>>(iter: T) -> Self {
let cstrings = iter.into_iter().collect_vec();
let mut pointers = cstrings.iter().map(|cs| cs.as_ptr()).collect_vec();
if IS_NULL_TERMINATED {
pointers.push(ptr::null());
}
MaybeNullTerminatedArrayOfStrings { cstrings, pointers }
}
}
pub(crate) type ArrayOfStrings = MaybeNullTerminatedArrayOfStrings<false>;
pub(crate) type NullTerminatedArrayOfStrings = MaybeNullTerminatedArrayOfStrings<true>;
#[cfg(test)]
mod tests {
use super::*;
use std::ptr::null_mut;
#[test]
fn array_all_ptrs_non_null() {
let array = ArrayOfStrings::new(["one", "two"]).unwrap();
assert_eq!(array.len(), 2);
assert!(!array.is_empty());
assert_ne!(array.as_ptr(), null_mut());
unsafe {
assert_ne!(*(array.as_ptr().add(0)), null_mut());
assert_ne!(*(array.as_ptr().add(1)), null_mut());
}
}
#[test]
fn array_empty_is_null() {
let source: &[String] = &[];
let array = ArrayOfStrings::new(source).unwrap();
assert_eq!(array.len(), 0);
assert!(array.is_empty());
assert_eq!(array.as_ptr(), null_mut());
}
#[test]
fn null_terminated_array_all_ptrs_non_null() {
let array = NullTerminatedArrayOfStrings::new(["one", "two"]).unwrap();
assert_eq!(array.len(), 2);
assert!(!array.is_empty());
assert_ne!(array.as_ptr(), null_mut());
unsafe {
assert_ne!(*(array.as_ptr().add(0)), null_mut());
assert_ne!(*(array.as_ptr().add(1)), null_mut());
assert_eq!(*(array.as_ptr().add(2)), null_mut());
}
}
#[test]
fn null_terminated_array_empty_is_null() {
let source: &[String] = &[];
let array = NullTerminatedArrayOfStrings::new(source).unwrap();
assert_eq!(array.len(), 0);
assert!(array.is_empty());
assert_eq!(array.as_ptr(), null_mut());
}
}