nautilus_core/ffi/
parsing.rs1use std::{
28 collections::HashMap,
29 ffi::{CStr, CString, c_char},
30};
31
32use serde_json::Value;
33use ustr::Ustr;
34
35use crate::{
36 ffi::{abort_on_panic, string::cstr_as_str},
37 parsing::{min_increment_precision_from_str, precision_from_str},
38};
39
40#[must_use]
51pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
52 assert!(!ptr.is_null(), "`ptr` was NULL");
53
54 let c_str = unsafe { CStr::from_ptr(ptr) };
56 let bytes = c_str.to_bytes();
57
58 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
59 let value: serde_json::Value =
60 serde_json::from_str(json_string).expect("C string contains invalid JSON");
61
62 let arr = value
63 .as_array()
64 .expect("C string JSON must be an array of strings");
65
66 arr.iter()
67 .map(|value| {
68 value
69 .as_str()
70 .expect("C string JSON array must contain only strings")
71 .to_owned()
72 })
73 .collect()
74}
75
76#[must_use]
82pub fn string_vec_to_bytes(strings: &[String]) -> *const c_char {
83 let json_string = serde_json::to_string(strings).expect("Failed to serialize strings to JSON");
84 let c_string = CString::new(json_string).expect("JSON string contains interior null bytes");
85
86 c_string.into_raw()
87}
88
89#[must_use]
99pub unsafe fn optional_bytes_to_json(ptr: *const c_char) -> Option<HashMap<String, Value>> {
100 if ptr.is_null() {
101 None
102 } else {
103 let c_str = unsafe { CStr::from_ptr(ptr) };
105 let bytes = c_str.to_bytes();
106
107 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
108 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
109
110 Some(result)
111 }
112}
113
114#[must_use]
124pub unsafe fn optional_bytes_to_str_map(ptr: *const c_char) -> Option<HashMap<Ustr, Ustr>> {
125 if ptr.is_null() {
126 None
127 } else {
128 let c_str = unsafe { CStr::from_ptr(ptr) };
130 let bytes = c_str.to_bytes();
131
132 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
133 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
134
135 Some(result)
136 }
137}
138
139#[must_use]
149pub unsafe fn optional_bytes_to_str_vec(ptr: *const c_char) -> Option<Vec<String>> {
150 if ptr.is_null() {
151 None
152 } else {
153 let c_str = unsafe { CStr::from_ptr(ptr) };
155 let bytes = c_str.to_bytes();
156
157 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
158 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
159
160 Some(result)
161 }
162}
163
164#[unsafe(no_mangle)]
174pub unsafe extern "C" fn precision_from_cstr(ptr: *const c_char) -> u8 {
175 abort_on_panic(|| {
176 assert!(!ptr.is_null(), "`ptr` was NULL");
177 let s = unsafe { cstr_as_str(ptr) };
179 precision_from_str(s)
180 })
181}
182
183#[unsafe(no_mangle)]
193pub unsafe extern "C" fn min_increment_precision_from_cstr(ptr: *const c_char) -> u8 {
194 abort_on_panic(|| {
195 assert!(!ptr.is_null(), "`ptr` was NULL");
196 let s = unsafe { cstr_as_str(ptr) };
198 min_increment_precision_from_str(s)
199 })
200}
201
202#[must_use]
204pub const fn u8_as_bool(value: u8) -> bool {
205 value != 0
206}
207
208#[cfg(test)]
209mod tests {
210 use std::ffi::CString;
211
212 use rstest::rstest;
213
214 use super::*;
215
216 #[rstest]
217 fn test_optional_bytes_to_json_null() {
218 let ptr = std::ptr::null();
219 let result = unsafe { optional_bytes_to_json(ptr) };
220 assert_eq!(result, None);
221 }
222
223 #[rstest]
224 fn test_optional_bytes_to_json_empty() {
225 let json_str = CString::new("{}").unwrap();
226 let ptr = json_str.as_ptr().cast::<c_char>();
227 let result = unsafe { optional_bytes_to_json(ptr) };
228 assert_eq!(result, Some(HashMap::new()));
229 }
230
231 #[rstest]
232 fn test_string_vec_to_bytes_valid() {
233 let strings = vec!["value1", "value2", "value3"]
234 .into_iter()
235 .map(String::from)
236 .collect::<Vec<String>>();
237
238 let ptr = string_vec_to_bytes(&strings);
239
240 let result = unsafe { bytes_to_string_vec(ptr) };
241 assert_eq!(result, strings);
242 }
243
244 #[rstest]
245 fn test_string_vec_to_bytes_empty() {
246 let strings = Vec::new();
247 let ptr = string_vec_to_bytes(&strings);
248
249 let result = unsafe { bytes_to_string_vec(ptr) };
250 assert_eq!(result, strings);
251 }
252
253 #[rstest]
254 fn test_bytes_to_string_vec_valid() {
255 let json_str = CString::new(r#"["value1", "value2", "value3"]"#).unwrap();
256 let ptr = json_str.as_ptr().cast::<c_char>();
257 let result = unsafe { bytes_to_string_vec(ptr) };
258
259 let expected_vec = vec!["value1", "value2", "value3"]
260 .into_iter()
261 .map(String::from)
262 .collect::<Vec<String>>();
263
264 assert_eq!(result, expected_vec);
265 }
266
267 #[rstest]
268 #[should_panic(expected = "array must contain only strings")]
269 fn test_bytes_to_string_vec_invalid() {
270 let json_str = CString::new(r#"["value1", 42, "value3"]"#).unwrap();
271 let ptr = json_str.as_ptr().cast::<c_char>();
272 let _ = unsafe { bytes_to_string_vec(ptr) };
273 }
274
275 #[rstest]
276 fn test_optional_bytes_to_json_valid() {
277 let json_str = CString::new(r#"{"key1": "value1", "key2": 2}"#).unwrap();
278 let ptr = json_str.as_ptr().cast::<c_char>();
279 let result = unsafe { optional_bytes_to_json(ptr) };
280 let mut expected_map = HashMap::new();
281 expected_map.insert("key1".to_owned(), Value::String("value1".to_owned()));
282 expected_map.insert(
283 "key2".to_owned(),
284 Value::Number(serde_json::Number::from(2)),
285 );
286 assert_eq!(result, Some(expected_map));
287 }
288
289 #[rstest]
290 #[should_panic(expected = "C string contains invalid JSON")]
291 fn test_optional_bytes_to_json_invalid() {
292 let json_str = CString::new(r#"{"key1": "value1", "key2": }"#).unwrap();
293 let ptr = json_str.as_ptr().cast::<c_char>();
294 let _result = unsafe { optional_bytes_to_json(ptr) };
295 }
296
297 #[rstest]
298 #[case("1e8", 0)]
299 #[case("123", 0)]
300 #[case("123.45", 2)]
301 #[case("123.456789", 6)]
302 #[case("1.23456789e-2", 2)]
303 #[case("1.23456789e-12", 12)]
304 fn test_precision_from_cstr(#[case] input: &str, #[case] expected: u8) {
305 let c_str = CString::new(input).unwrap();
306 assert_eq!(unsafe { precision_from_cstr(c_str.as_ptr()) }, expected);
307 }
308}