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]
50pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
51 assert!(!ptr.is_null(), "`ptr` was NULL");
52
53 let c_str = unsafe { CStr::from_ptr(ptr) };
54 let bytes = c_str.to_bytes();
55
56 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
57 let value: serde_json::Value =
58 serde_json::from_str(json_string).expect("C string contains invalid JSON");
59
60 match value {
61 serde_json::Value::Array(arr) => arr
62 .into_iter()
63 .filter_map(|value| match value {
64 serde_json::Value::String(string_value) => Some(string_value),
65 _ => None,
66 })
67 .collect(),
68 _ => Vec::new(),
69 }
70}
71
72#[must_use]
78pub fn string_vec_to_bytes(strings: &[String]) -> *const c_char {
79 let json_string = serde_json::to_string(strings).expect("Failed to serialize strings to JSON");
80 let c_string = CString::new(json_string).expect("JSON string contains interior null bytes");
81
82 c_string.into_raw()
83}
84
85#[must_use]
95pub unsafe fn optional_bytes_to_json(ptr: *const c_char) -> Option<HashMap<String, Value>> {
96 if ptr.is_null() {
97 None
98 } else {
99 let c_str = unsafe { CStr::from_ptr(ptr) };
100 let bytes = c_str.to_bytes();
101
102 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
103 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
104
105 Some(result)
106 }
107}
108
109#[must_use]
119pub unsafe fn optional_bytes_to_str_map(ptr: *const c_char) -> Option<HashMap<Ustr, Ustr>> {
120 if ptr.is_null() {
121 None
122 } else {
123 let c_str = unsafe { CStr::from_ptr(ptr) };
124 let bytes = c_str.to_bytes();
125
126 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
127 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
128
129 Some(result)
130 }
131}
132
133#[must_use]
143pub unsafe fn optional_bytes_to_str_vec(ptr: *const c_char) -> Option<Vec<String>> {
144 if ptr.is_null() {
145 None
146 } else {
147 let c_str = unsafe { CStr::from_ptr(ptr) };
148 let bytes = c_str.to_bytes();
149
150 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
151 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
152
153 Some(result)
154 }
155}
156
157#[unsafe(no_mangle)]
167pub unsafe extern "C" fn precision_from_cstr(ptr: *const c_char) -> u8 {
168 abort_on_panic(|| {
169 assert!(!ptr.is_null(), "`ptr` was NULL");
170 let s = unsafe { cstr_as_str(ptr) };
171 precision_from_str(s)
172 })
173}
174
175#[unsafe(no_mangle)]
185pub unsafe extern "C" fn min_increment_precision_from_cstr(ptr: *const c_char) -> u8 {
186 abort_on_panic(|| {
187 assert!(!ptr.is_null(), "`ptr` was NULL");
188 let s = unsafe { cstr_as_str(ptr) };
189 min_increment_precision_from_str(s)
190 })
191}
192
193#[must_use]
195pub const fn u8_as_bool(value: u8) -> bool {
196 value != 0
197}
198
199#[cfg(test)]
203mod tests {
204 use std::ffi::CString;
205
206 use rstest::rstest;
207
208 use super::*;
209
210 #[rstest]
211 fn test_optional_bytes_to_json_null() {
212 let ptr = std::ptr::null();
213 let result = unsafe { optional_bytes_to_json(ptr) };
214 assert_eq!(result, None);
215 }
216
217 #[rstest]
218 fn test_optional_bytes_to_json_empty() {
219 let json_str = CString::new("{}").unwrap();
220 let ptr = json_str.as_ptr().cast::<c_char>();
221 let result = unsafe { optional_bytes_to_json(ptr) };
222 assert_eq!(result, Some(HashMap::new()));
223 }
224
225 #[rstest]
226 fn test_string_vec_to_bytes_valid() {
227 let strings = vec!["value1", "value2", "value3"]
228 .into_iter()
229 .map(String::from)
230 .collect::<Vec<String>>();
231
232 let ptr = string_vec_to_bytes(&strings);
233
234 let result = unsafe { bytes_to_string_vec(ptr) };
235 assert_eq!(result, strings);
236 }
237
238 #[rstest]
239 fn test_string_vec_to_bytes_empty() {
240 let strings = Vec::new();
241 let ptr = string_vec_to_bytes(&strings);
242
243 let result = unsafe { bytes_to_string_vec(ptr) };
244 assert_eq!(result, strings);
245 }
246
247 #[rstest]
248 fn test_bytes_to_string_vec_valid() {
249 let json_str = CString::new(r#"["value1", "value2", "value3"]"#).unwrap();
250 let ptr = json_str.as_ptr().cast::<c_char>();
251 let result = unsafe { bytes_to_string_vec(ptr) };
252
253 let expected_vec = vec!["value1", "value2", "value3"]
254 .into_iter()
255 .map(String::from)
256 .collect::<Vec<String>>();
257
258 assert_eq!(result, expected_vec);
259 }
260
261 #[rstest]
262 fn test_bytes_to_string_vec_invalid() {
263 let json_str = CString::new(r#"["value1", 42, "value3"]"#).unwrap();
264 let ptr = json_str.as_ptr().cast::<c_char>();
265 let result = unsafe { bytes_to_string_vec(ptr) };
266
267 let expected_vec = vec!["value1", "value3"]
268 .into_iter()
269 .map(String::from)
270 .collect::<Vec<String>>();
271
272 assert_eq!(result, expected_vec);
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}