use super::types::{FFIResult, JSONEvalHandle};
use crate::JSONEval;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
#[no_mangle]
pub extern "C" fn json_eval_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_new_from_msgpack(
schema_msgpack: *const u8,
schema_len: usize,
context: *const c_char,
data: *const c_char,
) -> *mut JSONEvalHandle {
if schema_msgpack.is_null() || schema_len == 0 {
eprintln!("[FFI ERROR] json_eval_new_from_msgpack: invalid schema pointer or length");
return ptr::null_mut();
}
let schema_bytes = std::slice::from_raw_parts(schema_msgpack, schema_len);
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!(
"[FFI ERROR] json_eval_new_from_msgpack: invalid UTF-8 in context: {}",
e
);
return ptr::null_mut();
}
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!(
"[FFI ERROR] json_eval_new_from_msgpack: invalid UTF-8 in data: {}",
e
);
return ptr::null_mut();
}
}
} else {
None
};
match JSONEval::new_from_msgpack(schema_bytes, context_str, data_str) {
Ok(eval) => {
let handle = Box::new(JSONEvalHandle {
inner: Box::new(eval),
current_token: None,
});
Box::into_raw(handle)
}
Err(e) => {
let error_msg = format!("Failed to create JSONEval instance from MessagePack: {}", e);
eprintln!("[FFI ERROR] json_eval_new_from_msgpack: {}", error_msg);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_new(
schema: *const c_char,
context: *const c_char,
data: *const c_char,
) -> *mut JSONEvalHandle {
if schema.is_null() {
eprintln!("[FFI ERROR] json_eval_new: schema pointer is null");
return ptr::null_mut();
}
let schema_str = match CStr::from_ptr(schema).to_str() {
Ok(s) => s,
Err(e) => {
eprintln!("[FFI ERROR] json_eval_new: invalid UTF-8 in schema: {}", e);
return ptr::null_mut();
}
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!("[FFI ERROR] json_eval_new: invalid UTF-8 in context: {}", e);
return ptr::null_mut();
}
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!("[FFI ERROR] json_eval_new: invalid UTF-8 in data: {}", e);
return ptr::null_mut();
}
}
} else {
None
};
match JSONEval::new(schema_str, context_str, data_str) {
Ok(eval) => {
let handle = Box::new(JSONEvalHandle {
inner: Box::new(eval),
current_token: None,
});
Box::into_raw(handle)
}
Err(e) => {
let error_msg = format!("Failed to create JSONEval instance: {}", e);
eprintln!("[FFI ERROR] json_eval_new: {}", error_msg);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_new_with_error(
schema: *const c_char,
context: *const c_char,
data: *const c_char,
error_out: *mut *mut c_char,
) -> *mut JSONEvalHandle {
if schema.is_null() {
if !error_out.is_null() {
*error_out = CString::new("Schema pointer is null")
.unwrap_or_else(|_| CString::new("Null byte error").unwrap())
.into_raw();
}
return ptr::null_mut();
}
let schema_str = match CStr::from_ptr(schema).to_str() {
Ok(s) => s,
Err(e) => {
if !error_out.is_null() {
let msg = format!("Invalid UTF-8 in schema: {}", e);
*error_out = CString::new(msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in schema").unwrap())
.into_raw();
}
return ptr::null_mut();
}
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(e) => {
if !error_out.is_null() {
let msg = format!("Invalid UTF-8 in context: {}", e);
*error_out = CString::new(msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in context").unwrap())
.into_raw();
}
return ptr::null_mut();
}
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(e) => {
if !error_out.is_null() {
let msg = format!("Invalid UTF-8 in data: {}", e);
*error_out = CString::new(msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in data").unwrap())
.into_raw();
}
return ptr::null_mut();
}
}
} else {
None
};
match JSONEval::new(schema_str, context_str, data_str) {
Ok(eval) => {
let handle = Box::new(JSONEvalHandle {
inner: Box::new(eval),
current_token: None,
});
Box::into_raw(handle)
}
Err(e) => {
if !error_out.is_null() {
let msg = format!("Failed to create JSONEval instance: {}", e);
*error_out = CString::new(msg)
.unwrap_or_else(|_| CString::new("Failed to create JSONEval instance").unwrap())
.into_raw();
}
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_free_result(result: super::types::FFIResult) {
if !result._owned_data.is_null() {
drop(Box::from_raw(result._owned_data));
}
if !result.error.is_null() {
drop(CString::from_raw(result.error));
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
drop(CString::from_raw(ptr));
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_free(handle: *mut JSONEvalHandle) {
if !handle.is_null() {
drop(Box::from_raw(handle));
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_reload_schema(
handle: *mut JSONEvalHandle,
schema: *const c_char,
context: *const c_char,
data: *const c_char,
) -> FFIResult {
if handle.is_null() || schema.is_null() {
return FFIResult::error("Invalid handle or schema pointer".to_string());
}
let eval = &mut (*handle).inner;
let schema_str = match CStr::from_ptr(schema).to_str() {
Ok(s) => s,
Err(_) => return FFIResult::error("Invalid UTF-8 in schema".to_string()),
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in context".to_string()),
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in data".to_string()),
}
} else {
None
};
match eval.reload_schema(schema_str, context_str, data_str) {
Ok(_) => FFIResult::success(Vec::new()),
Err(e) => FFIResult::error(e),
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_reload_schema_msgpack(
handle: *mut JSONEvalHandle,
schema_msgpack: *const u8,
schema_len: usize,
context: *const c_char,
data: *const c_char,
) -> FFIResult {
if handle.is_null() || schema_msgpack.is_null() || schema_len == 0 {
return FFIResult::error("Invalid handle, schema pointer, or length".to_string());
}
let eval = &mut (*handle).inner;
let schema_bytes = std::slice::from_raw_parts(schema_msgpack, schema_len);
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in context".to_string()),
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in data".to_string()),
}
} else {
None
};
match eval.reload_schema_msgpack(schema_bytes, context_str, data_str) {
Ok(_) => FFIResult::success(Vec::new()),
Err(e) => FFIResult::error(e),
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_new_from_cache(
cache_key: *const c_char,
context: *const c_char,
data: *const c_char,
) -> *mut JSONEvalHandle {
if cache_key.is_null() {
eprintln!("[FFI ERROR] json_eval_new_from_cache: cache_key pointer is null");
return ptr::null_mut();
}
let key_str = match CStr::from_ptr(cache_key).to_str() {
Ok(s) => s,
Err(e) => {
eprintln!(
"[FFI ERROR] json_eval_new_from_cache: invalid UTF-8 in cache_key: {}",
e
);
return ptr::null_mut();
}
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!(
"[FFI ERROR] json_eval_new_from_cache: invalid UTF-8 in context: {}",
e
);
return ptr::null_mut();
}
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(e) => {
eprintln!(
"[FFI ERROR] json_eval_new_from_cache: invalid UTF-8 in data: {}",
e
);
return ptr::null_mut();
}
}
} else {
None
};
let parsed = match crate::PARSED_SCHEMA_CACHE.get(key_str) {
Some(p) => p,
None => {
eprintln!(
"[FFI ERROR] json_eval_new_from_cache: schema '{}' not found in cache",
key_str
);
return ptr::null_mut();
}
};
match crate::JSONEval::with_parsed_schema(parsed, context_str, data_str) {
Ok(eval) => {
let handle = Box::new(JSONEvalHandle {
inner: Box::new(eval),
current_token: None,
});
Box::into_raw(handle)
}
Err(e) => {
let error_msg = format!("Failed to create JSONEval instance from cache: {}", e);
eprintln!("[FFI ERROR] json_eval_new_from_cache: {}", error_msg);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_new_from_cache_with_error(
cache_key: *const c_char,
context: *const c_char,
data: *const c_char,
error_out: *mut *mut c_char,
) -> *mut JSONEvalHandle {
if cache_key.is_null() {
if !error_out.is_null() {
*error_out = CString::new("cache_key pointer is null")
.unwrap_or_else(|_| CString::new("Null byte error").unwrap())
.into_raw();
}
return ptr::null_mut();
}
let key_str = match CStr::from_ptr(cache_key).to_str() {
Ok(s) => s,
Err(e) => {
if !error_out.is_null() {
let error_msg = format!("Invalid UTF-8 in cache_key: {}", e);
*error_out = CString::new(error_msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in cache_key").unwrap())
.into_raw();
}
return ptr::null_mut();
}
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(e) => {
if !error_out.is_null() {
let error_msg = format!("Invalid UTF-8 in context: {}", e);
*error_out = CString::new(error_msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in context").unwrap())
.into_raw();
}
return ptr::null_mut();
}
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(e) => {
if !error_out.is_null() {
let error_msg = format!("Invalid UTF-8 in data: {}", e);
*error_out = CString::new(error_msg)
.unwrap_or_else(|_| CString::new("Invalid UTF-8 in data").unwrap())
.into_raw();
}
return ptr::null_mut();
}
}
} else {
None
};
let parsed = match crate::PARSED_SCHEMA_CACHE.get(key_str) {
Some(p) => p,
None => {
if !error_out.is_null() {
let error_msg = format!("Schema '{}' not found in cache", key_str);
*error_out = CString::new(error_msg)
.unwrap_or_else(|_| CString::new("Schema not found in cache").unwrap())
.into_raw();
}
return ptr::null_mut();
}
};
match crate::JSONEval::with_parsed_schema(parsed, context_str, data_str) {
Ok(eval) => {
if !error_out.is_null() {
*error_out = ptr::null_mut(); }
let handle = Box::new(JSONEvalHandle {
inner: Box::new(eval),
current_token: None,
});
Box::into_raw(handle)
}
Err(e) => {
if !error_out.is_null() {
let error_msg = format!("Failed to create JSONEval from cache: {}", e);
*error_out = CString::new(error_msg)
.unwrap_or_else(|_| {
CString::new("Failed to create JSONEval from cache").unwrap()
})
.into_raw();
}
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_reload_schema_from_cache(
handle: *mut JSONEvalHandle,
cache_key: *const c_char,
context: *const c_char,
data: *const c_char,
) -> FFIResult {
if handle.is_null() || cache_key.is_null() {
return FFIResult::error("Invalid handle or cache_key".to_string());
}
let eval = &mut (*handle).inner;
let key_str = match CStr::from_ptr(cache_key).to_str() {
Ok(s) => s,
Err(_) => return FFIResult::error("Invalid UTF-8 in cache_key".to_string()),
};
let context_str = if !context.is_null() {
match CStr::from_ptr(context).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in context".to_string()),
}
} else {
None
};
let data_str = if !data.is_null() {
match CStr::from_ptr(data).to_str() {
Ok(s) => Some(s),
Err(_) => return FFIResult::error("Invalid UTF-8 in data".to_string()),
}
} else {
None
};
match eval.reload_schema_from_cache(key_str, context_str, data_str) {
Ok(_) => FFIResult::success(Vec::new()),
Err(e) => FFIResult::error(e),
}
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_set_timezone_offset(
handle: *mut JSONEvalHandle,
offset_minutes: i32,
) {
if handle.is_null() {
eprintln!("[FFI ERROR] json_eval_set_timezone_offset: handle is null");
return;
}
let eval = &mut (*handle).inner;
let offset = if offset_minutes == i32::MIN {
None
} else {
Some(offset_minutes)
};
eval.set_timezone_offset(offset);
}
#[no_mangle]
pub unsafe extern "C" fn json_eval_cancel(handle: *mut JSONEvalHandle) {
if handle.is_null() {
return;
}
if let Some(token) = &(*handle).current_token {
token.cancel();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_safe_cstring_handling() {
let mut error_out: *mut c_char = ptr::null_mut();
unsafe fn set_error(error_out: *mut *mut c_char, msg: String) {
if !error_out.is_null() {
*error_out = CString::new(msg)
.unwrap_or_else(|_| CString::new("Fallback error message").unwrap())
.into_raw();
}
}
unsafe {
set_error(&mut error_out, "Normal error".to_string());
assert!(!error_out.is_null());
let s = CString::from_raw(error_out);
assert_eq!(s.to_str().unwrap(), "Normal error");
}
unsafe {
set_error(&mut error_out, "Error with\0 null byte".to_string());
assert!(!error_out.is_null());
let s = CString::from_raw(error_out);
assert_eq!(s.to_str().unwrap(), "Fallback error message");
}
}
}