use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;
use hdds::dds::filter::{ContentFilter, FieldValue};
use crate::HddsError;
#[repr(C)]
pub struct HddsFilter {
_private: [u8; 0],
}
#[repr(C)]
pub struct HddsFieldMap {
_private: [u8; 0],
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_create(
expression: *const c_char,
) -> *mut HddsFilter {
if expression.is_null() {
return ptr::null_mut();
}
let Ok(expr) = CStr::from_ptr(expression).to_str() else {
return ptr::null_mut();
};
match ContentFilter::new(expr) {
Ok(filter) => Box::into_raw(Box::new(filter)).cast::<HddsFilter>(),
Err(e) => {
log::error!("hdds_filter_create: {:?}", e);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_create_with_params(
expression: *const c_char,
params: *const *const c_char,
param_count: usize,
) -> *mut HddsFilter {
if expression.is_null() {
return ptr::null_mut();
}
let Ok(expr) = CStr::from_ptr(expression).to_str() else {
return ptr::null_mut();
};
let param_vec = collect_params(params, param_count);
match ContentFilter::with_parameters(expr, param_vec) {
Ok(filter) => Box::into_raw(Box::new(filter)).cast::<HddsFilter>(),
Err(e) => {
log::error!("hdds_filter_create_with_params: {:?}", e);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_set_parameters(
filter: *mut HddsFilter,
params: *const *const c_char,
count: usize,
) -> HddsError {
if filter.is_null() {
return HddsError::HddsInvalidArgument;
}
let f = &mut *filter.cast::<ContentFilter>();
let param_vec = collect_params(params, count);
f.set_parameters(param_vec);
HddsError::HddsOk
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_get_expression(
filter: *const HddsFilter,
out_buf: *mut c_char,
capacity: usize,
) -> usize {
if filter.is_null() {
return 0;
}
let f = &*filter.cast::<ContentFilter>();
let expr = f.expression();
let needed = expr.as_bytes().len();
if !out_buf.is_null() && capacity > 0 {
let copy_len = needed.min(capacity - 1);
ptr::copy_nonoverlapping(expr.as_bytes().as_ptr(), out_buf.cast::<u8>(), copy_len);
*out_buf.add(copy_len) = 0;
}
needed
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_destroy(filter: *mut HddsFilter) {
if !filter.is_null() {
let _ = Box::from_raw(filter.cast::<ContentFilter>());
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_create() -> *mut HddsFieldMap {
let map: HashMap<String, FieldValue> = HashMap::new();
Box::into_raw(Box::new(map)).cast::<HddsFieldMap>()
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_set_i64(
map: *mut HddsFieldMap,
name: *const c_char,
value: i64,
) -> HddsError {
field_map_set(map, name, FieldValue::Integer(value))
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_set_u64(
map: *mut HddsFieldMap,
name: *const c_char,
value: u64,
) -> HddsError {
field_map_set(map, name, FieldValue::Unsigned(value))
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_set_f64(
map: *mut HddsFieldMap,
name: *const c_char,
value: f64,
) -> HddsError {
field_map_set(map, name, FieldValue::Float(value))
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_set_bool(
map: *mut HddsFieldMap,
name: *const c_char,
value: bool,
) -> HddsError {
field_map_set(map, name, FieldValue::Boolean(value))
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_set_string(
map: *mut HddsFieldMap,
name: *const c_char,
value: *const c_char,
) -> HddsError {
if value.is_null() {
return HddsError::HddsInvalidArgument;
}
let Ok(val) = CStr::from_ptr(value).to_str() else {
return HddsError::HddsInvalidArgument;
};
field_map_set(map, name, FieldValue::String(val.to_string()))
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_clear(map: *mut HddsFieldMap) {
if !map.is_null() {
let m = &mut *map.cast::<HashMap<String, FieldValue>>();
m.clear();
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_field_map_destroy(map: *mut HddsFieldMap) {
if !map.is_null() {
let _ = Box::from_raw(map.cast::<HashMap<String, FieldValue>>());
}
}
#[no_mangle]
pub unsafe extern "C" fn hdds_filter_evaluate(
filter: *const HddsFilter,
map: *const HddsFieldMap,
) -> i32 {
if filter.is_null() || map.is_null() {
return -1;
}
let f = &*filter.cast::<ContentFilter>();
let m = &*map.cast::<HashMap<String, FieldValue>>();
let evaluator = f.evaluator();
match evaluator.matches(m) {
Ok(true) => 1,
Ok(false) => 0,
Err(e) => {
log::error!("hdds_filter_evaluate: {:?}", e);
-1
}
}
}
unsafe fn field_map_set(
map: *mut HddsFieldMap,
name: *const c_char,
value: FieldValue,
) -> HddsError {
if map.is_null() || name.is_null() {
return HddsError::HddsInvalidArgument;
}
let Ok(name_str) = CStr::from_ptr(name).to_str() else {
return HddsError::HddsInvalidArgument;
};
let m = &mut *map.cast::<HashMap<String, FieldValue>>();
m.insert(name_str.to_string(), value);
HddsError::HddsOk
}
unsafe fn collect_params(params: *const *const c_char, count: usize) -> Vec<String> {
if params.is_null() || count == 0 {
return Vec::new();
}
let mut result = Vec::with_capacity(count);
for i in 0..count {
let p = *params.add(i);
if !p.is_null() {
if let Ok(s) = CStr::from_ptr(p).to_str() {
result.push(s.to_string());
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_filter_create_and_destroy() {
unsafe {
let expr = CString::new("x > 10").unwrap();
let filter = hdds_filter_create(expr.as_ptr());
assert!(!filter.is_null());
hdds_filter_destroy(filter);
}
}
#[test]
fn test_filter_invalid_expression() {
unsafe {
let expr = CString::new("???!!!").unwrap();
let filter = hdds_filter_create(expr.as_ptr());
assert!(filter.is_null());
}
}
#[test]
fn test_filter_evaluate_simple() {
unsafe {
let expr = CString::new("temperature > 25").unwrap();
let filter = hdds_filter_create(expr.as_ptr());
assert!(!filter.is_null());
let map = hdds_field_map_create();
let fname = CString::new("temperature").unwrap();
hdds_field_map_set_f64(map, fname.as_ptr(), 30.0);
assert_eq!(hdds_filter_evaluate(filter, map), 1);
hdds_field_map_clear(map);
hdds_field_map_set_f64(map, fname.as_ptr(), 20.0);
assert_eq!(hdds_filter_evaluate(filter, map), 0);
hdds_field_map_destroy(map);
hdds_filter_destroy(filter);
}
}
#[test]
fn test_filter_with_parameters() {
unsafe {
let expr = CString::new("value > %0").unwrap();
let p0 = CString::new("42").unwrap();
let params = [p0.as_ptr()];
let filter = hdds_filter_create_with_params(expr.as_ptr(), params.as_ptr(), 1);
assert!(!filter.is_null());
let map = hdds_field_map_create();
let fname = CString::new("value").unwrap();
hdds_field_map_set_i64(map, fname.as_ptr(), 50);
assert_eq!(hdds_filter_evaluate(filter, map), 1);
let p_new = CString::new("100").unwrap();
let new_params = [p_new.as_ptr()];
hdds_filter_set_parameters(filter, new_params.as_ptr(), 1);
assert_eq!(hdds_filter_evaluate(filter, map), 0);
hdds_field_map_destroy(map);
hdds_filter_destroy(filter);
}
}
#[test]
fn test_filter_get_expression() {
unsafe {
let expr = CString::new("x > 10").unwrap();
let filter = hdds_filter_create(expr.as_ptr());
let mut buf = [0u8; 64];
let len = hdds_filter_get_expression(filter, buf.as_mut_ptr().cast(), 64);
assert_eq!(len, 6);
hdds_filter_destroy(filter);
}
}
#[test]
fn test_null_safety() {
unsafe {
assert!(hdds_filter_create(ptr::null()).is_null());
hdds_filter_destroy(ptr::null_mut());
hdds_field_map_destroy(ptr::null_mut());
assert_eq!(hdds_filter_evaluate(ptr::null(), ptr::null()), -1);
assert_eq!(
hdds_filter_set_parameters(ptr::null_mut(), ptr::null(), 0),
HddsError::HddsInvalidArgument,
);
assert_eq!(
hdds_field_map_set_f64(ptr::null_mut(), ptr::null(), 0.0),
HddsError::HddsInvalidArgument,
);
}
}
}