use super::bindings as ffi;
use crate::error::{CpdbError, Result};
use libc::c_char;
use std::ffi::{CStr, CString};
pub unsafe fn cstr_to_string(ptr: *const c_char) -> Result<String> {
if ptr.is_null() {
return Err(CpdbError::NullPointer);
}
Ok(unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned())
}
pub unsafe fn cstr_to_string_and_g_free(ptr: *mut c_char) -> Result<String> {
if ptr.is_null() {
return Err(CpdbError::NullPointer);
}
let owned = unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe { glib_sys::g_free(ptr as glib_sys::gpointer) };
Ok(owned)
}
pub struct COptions {
_strings: Box<[CString]>,
options: Box<[ffi::cpdb_option_t]>,
}
impl COptions {
pub fn as_mut_ptr(&mut self) -> *mut ffi::cpdb_option_t {
self.options.as_mut_ptr()
}
pub fn len(&self) -> usize {
self.options.len()
}
pub fn is_empty(&self) -> bool {
self.options.is_empty()
}
}
pub fn to_c_options(pairs: &[(&str, &str)]) -> Result<COptions> {
let mut strings: Vec<CString> = Vec::with_capacity(pairs.len() * 2);
for (key, value) in pairs {
strings.push(CString::new(*key)?);
strings.push(CString::new(*value)?);
}
let strings: Box<[CString]> = strings.into_boxed_slice();
let mut options: Vec<ffi::cpdb_option_t> = Vec::with_capacity(pairs.len());
for i in 0..pairs.len() {
let key_ptr = strings[i * 2].as_ptr() as *mut c_char;
let val_ptr = strings[i * 2 + 1].as_ptr() as *mut c_char;
options.push(ffi::cpdb_option_t {
option_name: key_ptr,
default_value: val_ptr,
group_name: std::ptr::null_mut(),
num_supported: 0,
supported_values: std::ptr::null_mut(),
});
}
Ok(COptions {
_strings: strings,
options: options.into_boxed_slice(),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn read_back(opts: &COptions) -> Vec<(String, String)> {
let mut out = Vec::with_capacity(opts.len());
for i in 0..opts.len() {
let entry = unsafe { &*opts.options.as_ptr().add(i) };
let name = unsafe { CStr::from_ptr(entry.option_name) }
.to_string_lossy()
.into_owned();
let value = unsafe { CStr::from_ptr(entry.default_value) }
.to_string_lossy()
.into_owned();
out.push((name, value));
}
out
}
#[test]
fn empty_input_yields_empty_options() {
let opts = to_c_options(&[]).unwrap();
assert!(opts.is_empty());
assert_eq!(opts.len(), 0);
assert!(read_back(&opts).is_empty());
}
#[test]
fn single_pair_round_trips() {
let opts = to_c_options(&[("copies", "2")]).unwrap();
assert_eq!(opts.len(), 1);
let echoed = read_back(&opts);
assert_eq!(echoed, vec![("copies".to_string(), "2".to_string())]);
}
#[test]
fn multiple_pairs_round_trip_in_order() {
let input = &[
("copies", "3"),
("sides", "two-sided-long-edge"),
("orientation-requested", "portrait"),
("media", "iso_a4_210x297mm"),
];
let opts = to_c_options(input).unwrap();
assert_eq!(opts.len(), input.len());
let echoed = read_back(&opts);
let expected: Vec<(String, String)> = input
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
assert_eq!(echoed, expected);
}
#[test]
fn interior_nul_in_key_is_rejected() {
let result = to_c_options(&[("co\0pies", "1")]);
assert!(matches!(result, Err(CpdbError::NulError(_))));
}
#[test]
fn interior_nul_in_value_is_rejected() {
let result = to_c_options(&[("copies", "1\0")]);
assert!(matches!(result, Err(CpdbError::NulError(_))));
}
#[test]
fn group_and_supported_fields_are_null_initialised() {
let opts = to_c_options(&[("a", "b")]).unwrap();
let entry = unsafe { &*opts.options.as_ptr() };
assert!(entry.group_name.is_null());
assert!(entry.supported_values.is_null());
assert_eq!(entry.num_supported, 0);
}
#[test]
fn pointers_stay_valid_across_move() {
let opts = to_c_options(&[("k", "v")]).unwrap();
let moved = opts;
let echoed = read_back(&moved);
assert_eq!(echoed, vec![("k".to_string(), "v".to_string())]);
}
}