nautilus_core/ffi/
parsing.rsuse std::{
collections::HashMap,
ffi::{c_char, CStr, CString},
};
use serde_json::{Result, Value};
use ustr::Ustr;
use crate::{
ffi::string::cstr_as_str,
parsing::{min_increment_precision_from_str, precision_from_str},
};
#[must_use]
pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
assert!(!ptr.is_null(), "`ptr` was NULL");
let c_str = CStr::from_ptr(ptr);
let bytes = c_str.to_bytes();
let json_string = std::str::from_utf8(bytes).unwrap();
let parsed_value: serde_json::Value = serde_json::from_str(json_string).unwrap();
match parsed_value {
serde_json::Value::Array(arr) => arr
.into_iter()
.filter_map(|value| match value {
serde_json::Value::String(string_value) => Some(string_value),
_ => None,
})
.collect(),
_ => Vec::new(),
}
}
#[must_use]
pub fn string_vec_to_bytes(strings: Vec<String>) -> *const c_char {
let json_string = serde_json::to_string(&strings).unwrap();
let c_string = CString::new(json_string).unwrap();
c_string.into_raw()
}
#[must_use]
pub unsafe fn optional_bytes_to_json(ptr: *const c_char) -> Option<HashMap<String, Value>> {
if ptr.is_null() {
None
} else {
let c_str = CStr::from_ptr(ptr);
let bytes = c_str.to_bytes();
let json_string = std::str::from_utf8(bytes).unwrap();
let result: Result<HashMap<String, Value>> = serde_json::from_str(json_string);
match result {
Ok(map) => Some(map),
Err(e) => {
eprintln!("Error parsing JSON: {e}");
None
}
}
}
}
#[must_use]
pub unsafe fn optional_bytes_to_str_map(ptr: *const c_char) -> Option<HashMap<Ustr, Ustr>> {
if ptr.is_null() {
None
} else {
let c_str = CStr::from_ptr(ptr);
let bytes = c_str.to_bytes();
let json_string = std::str::from_utf8(bytes).unwrap();
let result: Result<HashMap<Ustr, Ustr>> = serde_json::from_str(json_string);
match result {
Ok(map) => Some(map),
Err(e) => {
eprintln!("Error parsing JSON: {e}");
None
}
}
}
}
#[must_use]
pub unsafe fn optional_bytes_to_str_vec(ptr: *const c_char) -> Option<Vec<String>> {
if ptr.is_null() {
None
} else {
let c_str = CStr::from_ptr(ptr);
let bytes = c_str.to_bytes();
let json_string = std::str::from_utf8(bytes).unwrap();
let result: Result<Vec<String>> = serde_json::from_str(json_string);
match result {
Ok(map) => Some(map),
Err(e) => {
eprintln!("Error parsing JSON: {e}");
None
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn precision_from_cstr(ptr: *const c_char) -> u8 {
assert!(!ptr.is_null(), "`ptr` was NULL");
precision_from_str(cstr_as_str(ptr))
}
#[no_mangle]
pub unsafe extern "C" fn min_increment_precision_from_cstr(ptr: *const c_char) -> u8 {
assert!(!ptr.is_null(), "`ptr` was NULL");
min_increment_precision_from_str(cstr_as_str(ptr))
}
#[must_use]
pub const fn u8_as_bool(value: u8) -> bool {
value != 0
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use rstest::rstest;
use super::*;
#[rstest]
fn test_optional_bytes_to_json_null() {
let ptr = std::ptr::null();
let result = unsafe { optional_bytes_to_json(ptr) };
assert_eq!(result, None);
}
#[rstest]
fn test_optional_bytes_to_json_empty() {
let json_str = CString::new("{}").unwrap();
let ptr = json_str.as_ptr().cast::<c_char>();
let result = unsafe { optional_bytes_to_json(ptr) };
assert_eq!(result, Some(HashMap::new()));
}
#[rstest]
fn test_string_vec_to_bytes_valid() {
let strings = vec!["value1", "value2", "value3"]
.into_iter()
.map(String::from)
.collect::<Vec<String>>();
let ptr = string_vec_to_bytes(strings.clone());
let result = unsafe { bytes_to_string_vec(ptr) };
assert_eq!(result, strings);
}
#[rstest]
fn test_string_vec_to_bytes_empty() {
let strings = Vec::new();
let ptr = string_vec_to_bytes(strings.clone());
let result = unsafe { bytes_to_string_vec(ptr) };
assert_eq!(result, strings);
}
#[rstest]
fn test_bytes_to_string_vec_valid() {
let json_str = CString::new(r#"["value1", "value2", "value3"]"#).unwrap();
let ptr = json_str.as_ptr().cast::<c_char>();
let result = unsafe { bytes_to_string_vec(ptr) };
let expected_vec = vec!["value1", "value2", "value3"]
.into_iter()
.map(String::from)
.collect::<Vec<String>>();
assert_eq!(result, expected_vec);
}
#[rstest]
fn test_bytes_to_string_vec_invalid() {
let json_str = CString::new(r#"["value1", 42, "value3"]"#).unwrap();
let ptr = json_str.as_ptr().cast::<c_char>();
let result = unsafe { bytes_to_string_vec(ptr) };
let expected_vec = vec!["value1", "value3"]
.into_iter()
.map(String::from)
.collect::<Vec<String>>();
assert_eq!(result, expected_vec);
}
#[rstest]
fn test_optional_bytes_to_json_valid() {
let json_str = CString::new(r#"{"key1": "value1", "key2": 2}"#).unwrap();
let ptr = json_str.as_ptr().cast::<c_char>();
let result = unsafe { optional_bytes_to_json(ptr) };
let mut expected_map = HashMap::new();
expected_map.insert("key1".to_owned(), Value::String("value1".to_owned()));
expected_map.insert(
"key2".to_owned(),
Value::Number(serde_json::Number::from(2)),
);
assert_eq!(result, Some(expected_map));
}
#[rstest]
fn test_optional_bytes_to_json_invalid() {
let json_str = CString::new(r#"{"key1": "value1", "key2": }"#).unwrap();
let ptr = json_str.as_ptr().cast::<c_char>();
let result = unsafe { optional_bytes_to_json(ptr) };
assert_eq!(result, None);
}
#[rstest]
#[case("1e8", 0)]
#[case("123", 0)]
#[case("123.45", 2)]
#[case("123.456789", 6)]
#[case("1.23456789e-2", 2)]
#[case("1.23456789e-12", 12)]
fn test_precision_from_cstr(#[case] input: &str, #[case] expected: u8) {
let c_str = CString::new(input).unwrap();
assert_eq!(unsafe { precision_from_cstr(c_str.as_ptr()) }, expected);
}
}