nautilus_core/ffi/
parsing.rs1use std::{
17 collections::HashMap,
18 ffi::{CStr, CString, c_char},
19};
20
21use serde_json::Value;
22use ustr::Ustr;
23
24use crate::{
25 ffi::string::cstr_as_str,
26 parsing::{min_increment_precision_from_str, precision_from_str},
27};
28
29#[must_use]
39pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
40 assert!(!ptr.is_null(), "`ptr` was NULL");
41
42 let c_str = unsafe { CStr::from_ptr(ptr) };
43 let bytes = c_str.to_bytes();
44
45 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
46 let value: serde_json::Value =
47 serde_json::from_str(json_string).expect("C string contains invalid JSON");
48
49 match value {
50 serde_json::Value::Array(arr) => arr
51 .into_iter()
52 .filter_map(|value| match value {
53 serde_json::Value::String(string_value) => Some(string_value),
54 _ => None,
55 })
56 .collect(),
57 _ => Vec::new(),
58 }
59}
60
61#[must_use]
67pub fn string_vec_to_bytes(strings: &[String]) -> *const c_char {
68 let json_string = serde_json::to_string(strings).expect("Failed to serialize strings to JSON");
69 let c_string = CString::new(json_string).expect("JSON string contains interior null bytes");
70
71 c_string.into_raw()
72}
73
74#[must_use]
84pub unsafe fn optional_bytes_to_json(ptr: *const c_char) -> Option<HashMap<String, Value>> {
85 if ptr.is_null() {
86 None
87 } else {
88 let c_str = unsafe { CStr::from_ptr(ptr) };
89 let bytes = c_str.to_bytes();
90
91 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
92 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
93
94 Some(result)
95 }
96}
97
98#[must_use]
108pub unsafe fn optional_bytes_to_str_map(ptr: *const c_char) -> Option<HashMap<Ustr, Ustr>> {
109 if ptr.is_null() {
110 None
111 } else {
112 let c_str = unsafe { CStr::from_ptr(ptr) };
113 let bytes = c_str.to_bytes();
114
115 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
116 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
117
118 Some(result)
119 }
120}
121
122#[must_use]
132pub unsafe fn optional_bytes_to_str_vec(ptr: *const c_char) -> Option<Vec<String>> {
133 if ptr.is_null() {
134 None
135 } else {
136 let c_str = unsafe { CStr::from_ptr(ptr) };
137 let bytes = c_str.to_bytes();
138
139 let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
140 let result = serde_json::from_str(json_string).expect("C string contains invalid JSON");
141
142 Some(result)
143 }
144}
145
146#[unsafe(no_mangle)]
156pub unsafe extern "C" fn precision_from_cstr(ptr: *const c_char) -> u8 {
157 assert!(!ptr.is_null(), "`ptr` was NULL");
158 let s = unsafe { cstr_as_str(ptr) };
159 precision_from_str(s)
160}
161
162#[unsafe(no_mangle)]
172pub unsafe extern "C" fn min_increment_precision_from_cstr(ptr: *const c_char) -> u8 {
173 assert!(!ptr.is_null(), "`ptr` was NULL");
174 let s = unsafe { cstr_as_str(ptr) };
175 min_increment_precision_from_str(s)
176}
177
178#[must_use]
180pub const fn u8_as_bool(value: u8) -> bool {
181 value != 0
182}
183
184#[cfg(test)]
188mod tests {
189 use std::ffi::CString;
190
191 use rstest::rstest;
192
193 use super::*;
194
195 #[rstest]
196 fn test_optional_bytes_to_json_null() {
197 let ptr = std::ptr::null();
198 let result = unsafe { optional_bytes_to_json(ptr) };
199 assert_eq!(result, None);
200 }
201
202 #[rstest]
203 fn test_optional_bytes_to_json_empty() {
204 let json_str = CString::new("{}").unwrap();
205 let ptr = json_str.as_ptr().cast::<c_char>();
206 let result = unsafe { optional_bytes_to_json(ptr) };
207 assert_eq!(result, Some(HashMap::new()));
208 }
209
210 #[rstest]
211 fn test_string_vec_to_bytes_valid() {
212 let strings = vec!["value1", "value2", "value3"]
213 .into_iter()
214 .map(String::from)
215 .collect::<Vec<String>>();
216
217 let ptr = string_vec_to_bytes(&strings);
218
219 let result = unsafe { bytes_to_string_vec(ptr) };
220 assert_eq!(result, strings);
221 }
222
223 #[rstest]
224 fn test_string_vec_to_bytes_empty() {
225 let strings = Vec::new();
226 let ptr = string_vec_to_bytes(&strings);
227
228 let result = unsafe { bytes_to_string_vec(ptr) };
229 assert_eq!(result, strings);
230 }
231
232 #[rstest]
233 fn test_bytes_to_string_vec_valid() {
234 let json_str = CString::new(r#"["value1", "value2", "value3"]"#).unwrap();
235 let ptr = json_str.as_ptr().cast::<c_char>();
236 let result = unsafe { bytes_to_string_vec(ptr) };
237
238 let expected_vec = vec!["value1", "value2", "value3"]
239 .into_iter()
240 .map(String::from)
241 .collect::<Vec<String>>();
242
243 assert_eq!(result, expected_vec);
244 }
245
246 #[rstest]
247 fn test_bytes_to_string_vec_invalid() {
248 let json_str = CString::new(r#"["value1", 42, "value3"]"#).unwrap();
249 let ptr = json_str.as_ptr().cast::<c_char>();
250 let result = unsafe { bytes_to_string_vec(ptr) };
251
252 let expected_vec = vec!["value1", "value3"]
253 .into_iter()
254 .map(String::from)
255 .collect::<Vec<String>>();
256
257 assert_eq!(result, expected_vec);
258 }
259
260 #[rstest]
261 fn test_optional_bytes_to_json_valid() {
262 let json_str = CString::new(r#"{"key1": "value1", "key2": 2}"#).unwrap();
263 let ptr = json_str.as_ptr().cast::<c_char>();
264 let result = unsafe { optional_bytes_to_json(ptr) };
265 let mut expected_map = HashMap::new();
266 expected_map.insert("key1".to_owned(), Value::String("value1".to_owned()));
267 expected_map.insert(
268 "key2".to_owned(),
269 Value::Number(serde_json::Number::from(2)),
270 );
271 assert_eq!(result, Some(expected_map));
272 }
273
274 #[rstest]
275 #[should_panic(expected = "C string contains invalid JSON")]
276 fn test_optional_bytes_to_json_invalid() {
277 let json_str = CString::new(r#"{"key1": "value1", "key2": }"#).unwrap();
278 let ptr = json_str.as_ptr().cast::<c_char>();
279 let _result = unsafe { optional_bytes_to_json(ptr) };
280 }
281
282 #[rstest]
283 #[case("1e8", 0)]
284 #[case("123", 0)]
285 #[case("123.45", 2)]
286 #[case("123.456789", 6)]
287 #[case("1.23456789e-2", 2)]
288 #[case("1.23456789e-12", 12)]
289 fn test_precision_from_cstr(#[case] input: &str, #[case] expected: u8) {
290 let c_str = CString::new(input).unwrap();
291 assert_eq!(unsafe { precision_from_cstr(c_str.as_ptr()) }, expected);
292 }
293}