#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::ffi::{CStr, CString};
use std::ptr;
use chrono::{DateTime, Utc};
use libc::c_char;
use serde_json::Value as Json;
#[cfg(test)]
use crate::error;
use crate::error::{NemoFlowStatus, set_last_error};
pub fn c_str_to_json(ptr: *const c_char) -> Option<Json> {
if ptr.is_null() {
return Some(Json::Null);
}
let s = match unsafe { CStr::from_ptr(ptr) }.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(&format!("invalid UTF-8: {e}"));
return None;
}
};
match serde_json::from_str(s) {
Ok(v) => Some(v),
Err(e) => {
set_last_error(&format!("invalid JSON: {e}"));
None
}
}
}
pub fn c_str_to_opt_json(ptr: *const c_char) -> Option<Option<Json>> {
if ptr.is_null() {
return Some(None);
}
c_str_to_json(ptr).map(Some)
}
pub fn unix_micros_to_opt_timestamp(ptr: *const i64) -> Option<Option<DateTime<Utc>>> {
if ptr.is_null() {
return Some(None);
}
DateTime::<Utc>::from_timestamp_micros(unsafe { ptr::read(ptr) })
.map(Some)
.or_else(|| {
set_last_error("invalid timestamp: unix microseconds are outside supported range");
None
})
}
pub fn json_to_c_string(value: &Json) -> *mut c_char {
match serde_json::to_string(value) {
Ok(s) => CString::new(s).unwrap_or_default().into_raw(),
Err(_) => CString::new("null").unwrap().into_raw(),
}
}
pub fn str_to_c_string(s: &str) -> *mut c_char {
CString::new(s).unwrap_or_default().into_raw()
}
pub fn c_str_to_string(ptr: *const c_char) -> Result<String, NemoFlowStatus> {
if ptr.is_null() {
set_last_error("null string pointer");
return Err(NemoFlowStatus::NullPointer);
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.map(|s| s.to_string())
.map_err(|e| {
set_last_error(&format!("invalid UTF-8: {e}"));
NemoFlowStatus::InvalidUtf8
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_string_free(ptr: *mut c_char) {
if !ptr.is_null() {
drop(unsafe { CString::from_raw(ptr) });
}
}
#[cfg(test)]
#[path = "../tests/coverage/convert_tests.rs"]
mod tests;