1use super::bindings as ffi;
4use crate::error::{CpdbError, Result};
5use libc::c_char;
6use std::ffi::{CStr, CString};
7
8pub unsafe fn cstr_to_string(ptr: *const c_char) -> Result<String> {
18 if ptr.is_null() {
19 return Err(CpdbError::NullPointer);
20 }
21 Ok(unsafe { CStr::from_ptr(ptr) }
23 .to_string_lossy()
24 .into_owned())
25}
26
27pub unsafe fn cstr_to_string_and_g_free(ptr: *mut c_char) -> Result<String> {
36 if ptr.is_null() {
37 return Err(CpdbError::NullPointer);
38 }
39 let owned = unsafe { CStr::from_ptr(ptr) }
41 .to_string_lossy()
42 .into_owned();
43 unsafe { glib_sys::g_free(ptr as glib_sys::gpointer) };
44 Ok(owned)
45}
46
47pub struct COptions {
54 _strings: Box<[CString]>,
57 options: Box<[ffi::cpdb_option_t]>,
58}
59
60impl COptions {
61 pub fn as_mut_ptr(&mut self) -> *mut ffi::cpdb_option_t {
65 self.options.as_mut_ptr()
66 }
67
68 pub fn len(&self) -> usize {
70 self.options.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.options.is_empty()
76 }
77}
78
79pub fn to_c_options(pairs: &[(&str, &str)]) -> Result<COptions> {
86 let mut strings: Vec<CString> = Vec::with_capacity(pairs.len() * 2);
87 for (key, value) in pairs {
88 strings.push(CString::new(*key)?);
89 strings.push(CString::new(*value)?);
90 }
91 let strings: Box<[CString]> = strings.into_boxed_slice();
92
93 let mut options: Vec<ffi::cpdb_option_t> = Vec::with_capacity(pairs.len());
94 for i in 0..pairs.len() {
95 let key_ptr = strings[i * 2].as_ptr() as *mut c_char;
96 let val_ptr = strings[i * 2 + 1].as_ptr() as *mut c_char;
97 options.push(ffi::cpdb_option_t {
98 option_name: key_ptr,
99 default_value: val_ptr,
100 group_name: std::ptr::null_mut(),
101 num_supported: 0,
102 supported_values: std::ptr::null_mut(),
103 });
104 }
105
106 Ok(COptions {
107 _strings: strings,
108 options: options.into_boxed_slice(),
109 })
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
118
119 fn read_back(opts: &COptions) -> Vec<(String, String)> {
123 let mut out = Vec::with_capacity(opts.len());
124 for i in 0..opts.len() {
125 let entry = unsafe { &*opts.options.as_ptr().add(i) };
129 let name = unsafe { CStr::from_ptr(entry.option_name) }
130 .to_string_lossy()
131 .into_owned();
132 let value = unsafe { CStr::from_ptr(entry.default_value) }
133 .to_string_lossy()
134 .into_owned();
135 out.push((name, value));
136 }
137 out
138 }
139
140 #[test]
141 fn empty_input_yields_empty_options() {
142 let opts = to_c_options(&[]).unwrap();
143 assert!(opts.is_empty());
144 assert_eq!(opts.len(), 0);
145 assert!(read_back(&opts).is_empty());
146 }
147
148 #[test]
149 fn single_pair_round_trips() {
150 let opts = to_c_options(&[("copies", "2")]).unwrap();
151 assert_eq!(opts.len(), 1);
152 let echoed = read_back(&opts);
153 assert_eq!(echoed, vec![("copies".to_string(), "2".to_string())]);
154 }
155
156 #[test]
157 fn multiple_pairs_round_trip_in_order() {
158 let input = &[
159 ("copies", "3"),
160 ("sides", "two-sided-long-edge"),
161 ("orientation-requested", "portrait"),
162 ("media", "iso_a4_210x297mm"),
163 ];
164 let opts = to_c_options(input).unwrap();
165 assert_eq!(opts.len(), input.len());
166
167 let echoed = read_back(&opts);
168 let expected: Vec<(String, String)> = input
169 .iter()
170 .map(|(k, v)| (k.to_string(), v.to_string()))
171 .collect();
172 assert_eq!(echoed, expected);
173 }
174
175 #[test]
176 fn interior_nul_in_key_is_rejected() {
177 let result = to_c_options(&[("co\0pies", "1")]);
178 assert!(matches!(result, Err(CpdbError::NulError(_))));
179 }
180
181 #[test]
182 fn interior_nul_in_value_is_rejected() {
183 let result = to_c_options(&[("copies", "1\0")]);
184 assert!(matches!(result, Err(CpdbError::NulError(_))));
185 }
186
187 #[test]
188 fn group_and_supported_fields_are_null_initialised() {
189 let opts = to_c_options(&[("a", "b")]).unwrap();
190 let entry = unsafe { &*opts.options.as_ptr() };
192 assert!(entry.group_name.is_null());
193 assert!(entry.supported_values.is_null());
194 assert_eq!(entry.num_supported, 0);
195 }
196
197 #[test]
198 fn pointers_stay_valid_across_move() {
199 let opts = to_c_options(&[("k", "v")]).unwrap();
203 let moved = opts;
204 let echoed = read_back(&moved);
205 assert_eq!(echoed, vec![("k".to_string(), "v".to_string())]);
206 }
207}