use crate::event::{flatten_dynamic, FlattenStyle};
use indexmap::IndexMap;
use rhai::{Array, Dynamic, Engine, Map};
pub fn register_functions(engine: &mut Engine) {
engine.register_fn("sorted", sorted_array);
engine.register_fn("reversed", reversed_array);
engine.register_fn("slice", slice_array);
engine.register_fn("unique", unique_array);
engine.register_fn("sorted_by", sorted_by_field);
engine.register_fn("contains_any", contains_any_array);
engine.register_fn("starts_with_any", starts_with_any_array);
engine.register_fn("pluck", pluck);
engine.register_fn("pluck_as_nums", pluck_as_nums);
engine.register_fn("min", min_array);
engine.register_fn("max", max_array);
engine.register_fn("flattened", |array: Array| -> Map {
let dynamic_array = Dynamic::from(array);
let flattened = flatten_dynamic(&dynamic_array, FlattenStyle::default(), 0);
convert_indexmap_to_rhai_map(flattened)
});
engine.register_fn("flattened", |array: Array, style: &str| -> Map {
let flatten_style = match style {
"dot" => FlattenStyle::Dot,
"bracket" => FlattenStyle::Bracket,
"underscore" => FlattenStyle::Underscore,
_ => FlattenStyle::default(), };
let dynamic_array = Dynamic::from(array);
let flattened = flatten_dynamic(&dynamic_array, flatten_style, 0);
convert_indexmap_to_rhai_map(flattened)
});
engine.register_fn(
"flattened",
|array: Array, style: &str, max_depth: i64| -> Map {
let flatten_style = match style {
"dot" => FlattenStyle::Dot,
"bracket" => FlattenStyle::Bracket,
"underscore" => FlattenStyle::Underscore,
_ => FlattenStyle::default(),
};
let max_depth = if max_depth < 0 { 0 } else { max_depth as usize }; let dynamic_array = Dynamic::from(array);
let flattened = flatten_dynamic(&dynamic_array, flatten_style, max_depth);
convert_indexmap_to_rhai_map(flattened)
},
);
}
fn pluck(array: Array, field_name: String) -> Array {
let mut results = Array::new();
for item in array {
if let Some(map) = item.try_cast::<Map>() {
if let Some(value) = map.get(field_name.as_str()) {
if value.is_unit() {
continue;
}
results.push(value.clone());
}
}
}
results
}
fn pluck_as_nums(array: Array, field_name: String) -> Array {
let mut results = Array::new();
for item in array {
if let Some(map) = item.try_cast::<Map>() {
if let Some(value) = map.get(field_name.as_str()) {
if value.is_unit() {
continue;
}
if let Some(number) = dynamic_to_f64(value) {
results.push(Dynamic::from(number));
}
}
}
}
results
}
fn dynamic_to_f64(value: &Dynamic) -> Option<f64> {
if value.is_int() {
value.as_int().ok().map(|v| v as f64)
} else if value.is_float() {
value.as_float().ok()
} else if value.is_bool() {
value.as_bool().ok().map(|v| if v { 1.0 } else { 0.0 })
} else if value.is_string() {
value.clone().into_string().ok()?.parse::<f64>().ok()
} else {
value.to_string().parse::<f64>().ok()
}
}
fn sorted_array(mut arr: Array) -> Array {
arr.sort_by(|a, b| {
if a.is_unit() && b.is_unit() {
return std::cmp::Ordering::Equal;
}
if a.is_unit() {
return std::cmp::Ordering::Less;
}
if b.is_unit() {
return std::cmp::Ordering::Greater;
}
if let (Ok(a_num), Ok(b_num)) = (get_number_value(a), get_number_value(b)) {
return a_num
.partial_cmp(&b_num)
.unwrap_or(std::cmp::Ordering::Equal);
}
if get_number_value(a).is_ok() && get_number_value(b).is_err() {
return std::cmp::Ordering::Less;
}
if get_number_value(a).is_err() && get_number_value(b).is_ok() {
return std::cmp::Ordering::Greater;
}
let a_str = a.to_string();
let b_str = b.to_string();
a_str.cmp(&b_str)
});
arr
}
fn get_number_value(value: &Dynamic) -> Result<f64, ()> {
if value.is_int() {
Ok(value.as_int().unwrap_or(0) as f64)
} else if value.is_float() {
Ok(value.as_float().unwrap_or(0.0))
} else if value.is_string() {
let str_val = value.clone().into_string().unwrap_or_default();
str_val.parse::<f64>().map_err(|_| ())
} else {
Err(())
}
}
fn reversed_array(mut arr: Array) -> Array {
arr.reverse();
arr
}
fn slice_array(arr: Array, spec: &str) -> Array {
let len = arr.len() as i32;
if len == 0 {
return Array::new();
}
let parts: Vec<&str> = spec.split(':').collect();
let step = if parts.len() > 2 && !parts[2].trim().is_empty() {
parts[2].trim().parse::<i32>().unwrap_or(1)
} else {
1
};
if step == 0 {
return Array::new();
}
let (default_start, default_end) = if step > 0 { (0, len) } else { (len - 1, -1) };
let start = if !parts.is_empty() && !parts[0].trim().is_empty() {
let mut value = parts[0].trim().parse::<i32>().unwrap_or(default_start);
if value < 0 {
value += len;
}
if step > 0 {
value.clamp(0, len)
} else {
value.clamp(0, len - 1)
}
} else {
default_start
};
let end = if parts.len() > 1 && !parts[1].trim().is_empty() {
let mut value = parts[1].trim().parse::<i32>().unwrap_or(default_end);
if value < 0 {
value += len;
}
if step > 0 {
value.clamp(0, len)
} else {
value.clamp(-1, len - 1)
}
} else {
default_end
};
let mut result = Array::new();
let mut i = start;
if step > 0 {
while i < end {
if i >= 0 && i < len {
if let Some(value) = arr.get(i as usize) {
result.push(value.clone());
}
}
i += step;
}
} else {
while i > end {
if i >= 0 && i < len {
if let Some(value) = arr.get(i as usize) {
result.push(value.clone());
}
}
i += step;
}
}
result
}
fn unique_array(arr: Array) -> Array {
let mut seen = std::collections::HashSet::new();
let mut result = Array::new();
for item in arr {
let item_str = item.to_string();
if seen.insert(item_str) {
result.push(item);
}
}
result
}
fn sorted_by_field(mut arr: Array, field_name: String) -> Array {
arr.sort_by(|a, b| {
let a_field = extract_field_value(a, &field_name);
let b_field = extract_field_value(b, &field_name);
match (a_field, b_field) {
(None, None) => std::cmp::Ordering::Equal,
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(Some(a_val), Some(b_val)) => {
compare_dynamic_values(&a_val, &b_val)
}
}
});
arr
}
fn extract_field_value(obj: &Dynamic, field_name: &str) -> Option<Dynamic> {
if let Some(map) = obj.clone().try_cast::<rhai::Map>() {
map.get(field_name).cloned()
} else {
None
}
}
fn compare_dynamic_values(a: &Dynamic, b: &Dynamic) -> std::cmp::Ordering {
if a.is_unit() && b.is_unit() {
return std::cmp::Ordering::Equal;
}
if a.is_unit() {
return std::cmp::Ordering::Less;
}
if b.is_unit() {
return std::cmp::Ordering::Greater;
}
if let (Ok(a_num), Ok(b_num)) = (get_number_value(a), get_number_value(b)) {
return a_num
.partial_cmp(&b_num)
.unwrap_or(std::cmp::Ordering::Equal);
}
if get_number_value(a).is_ok() && get_number_value(b).is_err() {
return std::cmp::Ordering::Less;
}
if get_number_value(a).is_err() && get_number_value(b).is_ok() {
return std::cmp::Ordering::Greater;
}
let a_str = a.to_string();
let b_str = b.to_string();
a_str.cmp(&b_str)
}
fn contains_any_array(arr: Array, search_values: Array) -> bool {
let search_strings: Vec<String> = search_values.iter().map(|v| v.to_string()).collect();
arr.iter().any(|item| {
let item_str = item.to_string();
search_strings.contains(&item_str)
})
}
fn starts_with_any_array(arr: Array, search_values: Array) -> bool {
if arr.is_empty() || search_values.is_empty() {
return false;
}
let search_strings: Vec<String> = search_values.iter().map(|v| v.to_string()).collect();
let first_element_str = arr[0].to_string();
search_strings.contains(&first_element_str)
}
fn min_array(arr: Array) -> Dynamic {
if arr.is_empty() {
return Dynamic::UNIT;
}
let array_type = determine_array_type(&arr);
match array_type {
ArrayType::Empty => Dynamic::UNIT,
ArrayType::Mixed => Dynamic::UNIT, ArrayType::Numeric => find_numeric_min(&arr),
ArrayType::String => find_string_min(&arr),
}
}
fn max_array(arr: Array) -> Dynamic {
if arr.is_empty() {
return Dynamic::UNIT;
}
let array_type = determine_array_type(&arr);
match array_type {
ArrayType::Empty => Dynamic::UNIT,
ArrayType::Mixed => Dynamic::UNIT, ArrayType::Numeric => find_numeric_max(&arr),
ArrayType::String => find_string_max(&arr),
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum ArrayType {
Empty,
Numeric, String, Mixed, }
pub(crate) fn determine_array_type(arr: &Array) -> ArrayType {
if arr.is_empty() {
return ArrayType::Empty;
}
let mut has_numeric = false;
let mut has_string = false;
for item in arr {
if is_actual_number(item) {
has_numeric = true;
} else {
has_string = true;
}
if has_numeric && has_string {
return ArrayType::Mixed;
}
}
if has_numeric {
ArrayType::Numeric
} else {
ArrayType::String
}
}
pub(crate) fn is_actual_number(value: &Dynamic) -> bool {
value.is_int() || value.is_float()
}
fn find_numeric_min(arr: &Array) -> Dynamic {
let mut min_val = f64::INFINITY;
let mut min_item = &arr[0];
for item in arr {
let num = if item.is_int() {
item.as_int().unwrap_or(0) as f64
} else if item.is_float() {
item.as_float().unwrap_or(0.0)
} else {
continue; };
if num < min_val {
min_val = num;
min_item = item;
}
}
min_item.clone()
}
fn find_numeric_max(arr: &Array) -> Dynamic {
let mut max_val = f64::NEG_INFINITY;
let mut max_item = &arr[0];
for item in arr {
let num = if item.is_int() {
item.as_int().unwrap_or(0) as f64
} else if item.is_float() {
item.as_float().unwrap_or(0.0)
} else {
continue; };
if num > max_val {
max_val = num;
max_item = item;
}
}
max_item.clone()
}
fn find_string_min(arr: &Array) -> Dynamic {
let mut min_str = arr[0].to_string();
let mut min_item = &arr[0];
for item in arr {
let item_str = item.to_string();
if item_str < min_str {
min_str = item_str;
min_item = item;
}
}
min_item.clone()
}
fn find_string_max(arr: &Array) -> Dynamic {
let mut max_str = arr[0].to_string();
let mut max_item = &arr[0];
for item in arr {
let item_str = item.to_string();
if item_str > max_str {
max_str = item_str;
max_item = item;
}
}
max_item.clone()
}
fn convert_indexmap_to_rhai_map(indexmap: IndexMap<String, Dynamic>) -> Map {
let mut map = Map::new();
for (key, value) in indexmap {
map.insert(key.into(), value);
}
map
}
#[cfg(test)]
mod tests {
use super::*;
use rhai::{Array, Dynamic, Map};
fn make_row(fields: Vec<(&str, Dynamic)>) -> Dynamic {
let mut map = Map::new();
for (key, value) in fields {
map.insert(key.into(), value);
}
Dynamic::from(map)
}
#[test]
fn test_pluck_preserves_types() {
let rows = vec![
make_row(vec![
("status", Dynamic::from(200i64)),
("message", Dynamic::from("ok")),
]),
make_row(vec![
("status", Dynamic::from(404i64)),
("message", Dynamic::from("missing")),
]),
make_row(vec![("status", Dynamic::from(true))]),
];
let result = pluck(rows, "status".to_string());
assert_eq!(result.len(), 3);
assert_eq!(result[0].as_int().unwrap(), 200);
assert_eq!(result[1].as_int().unwrap(), 404);
assert!(result[2].as_bool().unwrap());
}
#[test]
fn test_pluck_skips_missing_and_unit() {
let rows = vec![
make_row(vec![("status", Dynamic::from(200i64))]),
make_row(vec![("status", Dynamic::from(()))]),
make_row(vec![("other", Dynamic::from("skip"))]),
Dynamic::from("not a map"),
];
let result = pluck(rows, "status".to_string());
assert_eq!(result.len(), 1);
assert_eq!(result[0].as_int().unwrap(), 200);
}
#[test]
fn test_pluck_as_nums_converts_mixed_types() {
let rows = vec![
make_row(vec![("value", Dynamic::from(42i64))]),
make_row(vec![("value", Dynamic::from(2.5))]),
make_row(vec![("value", Dynamic::from("3.5"))]),
make_row(vec![("value", Dynamic::from(true))]),
];
let result = pluck_as_nums(rows, "value".to_string());
assert_eq!(result.len(), 4);
assert_eq!(result[0].as_float().unwrap(), 42.0);
assert!((result[1].as_float().unwrap() - 2.5).abs() < f64::EPSILON);
assert!((result[2].as_float().unwrap() - 3.5).abs() < f64::EPSILON);
assert_eq!(result[3].as_float().unwrap(), 1.0);
}
#[test]
fn test_pluck_as_nums_skips_invalid_values() {
let rows = vec![
make_row(vec![("value", Dynamic::from("100"))]),
make_row(vec![("value", Dynamic::from("invalid"))]),
make_row(vec![("value", Dynamic::from(()))]),
Dynamic::from("not a map"),
];
let result = pluck_as_nums(rows, "value".to_string());
assert_eq!(result.len(), 1);
assert_eq!(result[0].as_float().unwrap(), 100.0);
}
#[test]
fn test_sorted_numbers() {
let arr = vec![
Dynamic::from(3i64),
Dynamic::from(1i64),
Dynamic::from(4i64),
Dynamic::from(1i64),
Dynamic::from(5i64),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 5);
assert_eq!(sorted[0].as_int().unwrap(), 1i64);
assert_eq!(sorted[1].as_int().unwrap(), 1i64);
assert_eq!(sorted[2].as_int().unwrap(), 3i64);
assert_eq!(sorted[3].as_int().unwrap(), 4i64);
assert_eq!(sorted[4].as_int().unwrap(), 5i64);
}
#[test]
fn test_sorted_strings() {
let arr = vec![
Dynamic::from("banana"),
Dynamic::from("apple"),
Dynamic::from("cherry"),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].clone().into_string().unwrap(), "apple");
assert_eq!(sorted[1].clone().into_string().unwrap(), "banana");
assert_eq!(sorted[2].clone().into_string().unwrap(), "cherry");
}
#[test]
fn test_sorted_mixed_types() {
let arr = vec![
Dynamic::from(3i64),
Dynamic::from("banana"),
Dynamic::from(1i64),
Dynamic::from("apple"),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 4);
assert_eq!(sorted[0].as_int().unwrap(), 1i64);
assert_eq!(sorted[1].as_int().unwrap(), 3i64);
assert_eq!(sorted[2].to_string(), "apple");
assert_eq!(sorted[3].to_string(), "banana");
}
#[test]
fn test_sorted_with_floats() {
let arr = vec![
Dynamic::from(std::f64::consts::PI),
Dynamic::from(1.5),
Dynamic::from(std::f64::consts::E),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].as_float().unwrap(), 1.5);
assert_eq!(sorted[1].as_float().unwrap(), std::f64::consts::E);
assert_eq!(sorted[2].as_float().unwrap(), std::f64::consts::PI);
}
#[test]
fn test_sorted_with_string_numbers() {
let arr = vec![
Dynamic::from("10"),
Dynamic::from("2"),
Dynamic::from("100"),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].to_string(), "2");
assert_eq!(sorted[1].to_string(), "10");
assert_eq!(sorted[2].to_string(), "100");
}
#[test]
fn test_sorted_empty_array() {
let arr = Array::new();
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 0);
}
#[test]
fn test_sorted_single_element() {
let arr = vec![Dynamic::from("single")];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 1);
assert_eq!(sorted[0].to_string(), "single");
}
#[test]
fn test_sorted_with_booleans() {
let arr = vec![
Dynamic::from(true),
Dynamic::from(false),
Dynamic::from("apple"),
];
let sorted = sorted_array(arr);
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].to_string(), "apple");
assert_eq!(sorted[1].to_string(), "false");
assert_eq!(sorted[2].to_string(), "true");
}
#[test]
fn test_reversed_basic() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
Dynamic::from(4i64),
Dynamic::from(5i64),
];
let reversed = reversed_array(arr);
assert_eq!(reversed.len(), 5);
assert_eq!(reversed[0].as_int().unwrap(), 5i64);
assert_eq!(reversed[1].as_int().unwrap(), 4i64);
assert_eq!(reversed[2].as_int().unwrap(), 3i64);
assert_eq!(reversed[3].as_int().unwrap(), 2i64);
assert_eq!(reversed[4].as_int().unwrap(), 1i64);
}
#[test]
fn test_reversed_strings() {
let arr = vec![
Dynamic::from("first"),
Dynamic::from("second"),
Dynamic::from("third"),
];
let reversed = reversed_array(arr);
assert_eq!(reversed.len(), 3);
assert_eq!(reversed[0].to_string(), "third");
assert_eq!(reversed[1].to_string(), "second");
assert_eq!(reversed[2].to_string(), "first");
}
#[test]
fn test_reversed_empty_array() {
let arr = Array::new();
let reversed = reversed_array(arr);
assert_eq!(reversed.len(), 0);
}
#[test]
fn test_unique_numbers() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
Dynamic::from(2i64),
Dynamic::from(1i64),
];
let unique = unique_array(arr);
assert_eq!(unique.len(), 3);
assert_eq!(unique[0].as_int().unwrap(), 1i64);
assert_eq!(unique[1].as_int().unwrap(), 2i64);
assert_eq!(unique[2].as_int().unwrap(), 3i64);
}
#[test]
fn test_unique_strings() {
let arr = vec![
Dynamic::from("bug"),
Dynamic::from("urgent"),
Dynamic::from("bug"),
Dynamic::from("frontend"),
Dynamic::from("urgent"),
];
let unique = unique_array(arr);
assert_eq!(unique.len(), 3);
assert_eq!(unique[0].to_string(), "bug");
assert_eq!(unique[1].to_string(), "urgent");
assert_eq!(unique[2].to_string(), "frontend");
}
#[test]
fn test_unique_empty_array() {
let arr = Array::new();
let unique = unique_array(arr);
assert_eq!(unique.len(), 0);
}
#[test]
fn test_unique_no_duplicates() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
];
let unique = unique_array(arr.clone());
assert_eq!(unique.len(), 3);
assert_eq!(unique[0].as_int().unwrap(), 1i64);
assert_eq!(unique[1].as_int().unwrap(), 2i64);
assert_eq!(unique[2].as_int().unwrap(), 3i64);
}
#[test]
fn test_unique_all_duplicates() {
let arr = vec![
Dynamic::from(42i64),
Dynamic::from(42i64),
Dynamic::from(42i64),
];
let unique = unique_array(arr);
assert_eq!(unique.len(), 1);
assert_eq!(unique[0].as_int().unwrap(), 42i64);
}
#[test]
fn test_unique_mixed_types() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from("hello"),
Dynamic::from(1i64),
Dynamic::from("hello"),
Dynamic::from(true),
];
let unique = unique_array(arr);
assert_eq!(unique.len(), 3);
assert_eq!(unique[0].as_int().unwrap(), 1i64);
assert_eq!(unique[1].to_string(), "hello");
assert!(unique[2].as_bool().unwrap());
}
#[test]
fn test_unique_preserves_order() {
let arr = vec![
Dynamic::from("z"),
Dynamic::from("a"),
Dynamic::from("m"),
Dynamic::from("a"),
Dynamic::from("z"),
];
let unique = unique_array(arr);
assert_eq!(unique.len(), 3);
assert_eq!(unique[0].to_string(), "z");
assert_eq!(unique[1].to_string(), "a");
assert_eq!(unique[2].to_string(), "m");
}
#[test]
fn test_sorted_by_numeric_field() {
let mut arr = Array::new();
let mut obj1 = rhai::Map::new();
obj1.insert("name".into(), Dynamic::from("alice"));
obj1.insert("age".into(), Dynamic::from(30i64));
arr.push(Dynamic::from(obj1));
let mut obj2 = rhai::Map::new();
obj2.insert("name".into(), Dynamic::from("bob"));
obj2.insert("age".into(), Dynamic::from(25i64));
arr.push(Dynamic::from(obj2));
let mut obj3 = rhai::Map::new();
obj3.insert("name".into(), Dynamic::from("charlie"));
obj3.insert("age".into(), Dynamic::from(35i64));
arr.push(Dynamic::from(obj3));
let sorted = sorted_by_field(arr, "age".to_string());
assert_eq!(sorted.len(), 3);
if let Some(obj) = sorted[0].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "bob");
assert_eq!(obj.get("age").unwrap().as_int().unwrap(), 25i64);
}
if let Some(obj) = sorted[1].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "alice");
assert_eq!(obj.get("age").unwrap().as_int().unwrap(), 30i64);
}
if let Some(obj) = sorted[2].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "charlie");
assert_eq!(obj.get("age").unwrap().as_int().unwrap(), 35i64);
}
}
#[test]
fn test_sorted_by_string_field() {
let mut arr = Array::new();
let mut obj1 = rhai::Map::new();
obj1.insert("name".into(), Dynamic::from("charlie"));
obj1.insert("score".into(), Dynamic::from(78i64));
arr.push(Dynamic::from(obj1));
let mut obj2 = rhai::Map::new();
obj2.insert("name".into(), Dynamic::from("alice"));
obj2.insert("score".into(), Dynamic::from(85i64));
arr.push(Dynamic::from(obj2));
let mut obj3 = rhai::Map::new();
obj3.insert("name".into(), Dynamic::from("bob"));
obj3.insert("score".into(), Dynamic::from(92i64));
arr.push(Dynamic::from(obj3));
let sorted = sorted_by_field(arr, "name".to_string());
assert_eq!(sorted.len(), 3);
if let Some(obj) = sorted[0].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "alice");
}
if let Some(obj) = sorted[1].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "bob");
}
if let Some(obj) = sorted[2].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "charlie");
}
}
#[test]
fn test_sorted_by_missing_field() {
let mut arr = Array::new();
let mut obj1 = rhai::Map::new();
obj1.insert("name".into(), Dynamic::from("alice"));
obj1.insert("age".into(), Dynamic::from(30i64));
arr.push(Dynamic::from(obj1));
let mut obj2 = rhai::Map::new();
obj2.insert("name".into(), Dynamic::from("bob"));
arr.push(Dynamic::from(obj2));
let mut obj3 = rhai::Map::new();
obj3.insert("name".into(), Dynamic::from("charlie"));
obj3.insert("age".into(), Dynamic::from(25i64));
arr.push(Dynamic::from(obj3));
let sorted = sorted_by_field(arr, "age".to_string());
assert_eq!(sorted.len(), 3);
if let Some(obj) = sorted[0].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "bob"); }
if let Some(obj) = sorted[1].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "charlie"); }
if let Some(obj) = sorted[2].clone().try_cast::<rhai::Map>() {
assert_eq!(obj.get("name").unwrap().to_string(), "alice"); }
}
#[test]
fn test_array_flatten_simple() {
let arr = vec![
Dynamic::from("item1"),
Dynamic::from("item2"),
Dynamic::from(42i64),
];
let flattened = {
let dynamic_array = Dynamic::from(arr);
let result = flatten_dynamic(&dynamic_array, FlattenStyle::Bracket, 10);
convert_indexmap_to_rhai_map(result)
};
assert_eq!(flattened.get("[0]").unwrap().to_string(), "item1");
assert_eq!(flattened.get("[1]").unwrap().to_string(), "item2");
assert_eq!(flattened.get("[2]").unwrap().to_string(), "42");
}
#[test]
fn test_array_flatten_nested() {
let mut inner1 = Map::new();
inner1.insert("id".into(), Dynamic::from(1i64));
inner1.insert("name".into(), Dynamic::from("first"));
let mut inner2 = Map::new();
inner2.insert("id".into(), Dynamic::from(2i64));
inner2.insert("name".into(), Dynamic::from("second"));
let arr = vec![Dynamic::from(inner1), Dynamic::from(inner2)];
let flattened = {
let dynamic_array = Dynamic::from(arr);
let result = flatten_dynamic(&dynamic_array, FlattenStyle::Bracket, 10);
convert_indexmap_to_rhai_map(result)
};
assert_eq!(flattened.get("[0].id").unwrap().to_string(), "1");
assert_eq!(flattened.get("[0].name").unwrap().to_string(), "first");
assert_eq!(flattened.get("[1].id").unwrap().to_string(), "2");
assert_eq!(flattened.get("[1].name").unwrap().to_string(), "second");
}
#[test]
fn test_array_flatten_styles() {
let mut inner = Map::new();
inner.insert("value".into(), Dynamic::from(42i64));
let arr = vec![Dynamic::from(inner)];
let dynamic_array = Dynamic::from(arr);
let bracket = flatten_dynamic(&dynamic_array, FlattenStyle::Bracket, 10);
assert!(bracket.contains_key("[0].value"));
let dot = flatten_dynamic(&dynamic_array, FlattenStyle::Dot, 10);
assert!(dot.contains_key("0.value"));
let underscore = flatten_dynamic(&dynamic_array, FlattenStyle::Underscore, 10);
assert!(underscore.contains_key("0_value"));
}
#[test]
fn test_contains_any_basic() {
let arr = vec![
Dynamic::from("urgent"),
Dynamic::from("bug"),
Dynamic::from("frontend"),
];
let search = vec![Dynamic::from("urgent"), Dynamic::from("critical")];
assert!(contains_any_array(arr, search));
}
#[test]
fn test_contains_any_no_match() {
let arr = vec![Dynamic::from("info"), Dynamic::from("debug")];
let search = vec![Dynamic::from("error"), Dynamic::from("warning")];
assert!(!contains_any_array(arr, search));
}
#[test]
fn test_contains_any_numbers() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
];
let search = vec![Dynamic::from(2i64), Dynamic::from(5i64)];
assert!(contains_any_array(arr, search));
}
#[test]
fn test_contains_any_mixed_types() {
let arr = vec![
Dynamic::from(1i64),
Dynamic::from("hello"),
Dynamic::from(true),
];
let search = vec![Dynamic::from("hello"), Dynamic::from(99i64)];
assert!(contains_any_array(arr, search));
}
#[test]
fn test_contains_any_string_number_conversion() {
let arr = vec![Dynamic::from(42i64)];
let search = vec![Dynamic::from("42")];
assert!(contains_any_array(arr, search));
}
#[test]
fn test_contains_any_boolean_conversion() {
let arr = vec![Dynamic::from(true), Dynamic::from(false)];
let search = vec![Dynamic::from("true")];
assert!(contains_any_array(arr, search));
}
#[test]
fn test_contains_any_empty_arrays() {
let arr = Array::new();
let search = Array::new();
assert!(!contains_any_array(arr, search));
let arr = vec![Dynamic::from("test")];
let search = Array::new();
assert!(!contains_any_array(arr, search));
let arr = Array::new();
let search = vec![Dynamic::from("test")];
assert!(!contains_any_array(arr, search));
}
#[test]
fn test_starts_with_any_basic() {
let arr = vec![
Dynamic::from("ERROR"),
Dynamic::from("Database connection failed"),
];
let search = vec![Dynamic::from("ERROR"), Dynamic::from("FATAL")];
assert!(starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_no_match() {
let arr = vec![Dynamic::from("INFO"), Dynamic::from("System started")];
let search = vec![Dynamic::from("ERROR"), Dynamic::from("WARNING")];
assert!(!starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_numbers() {
let arr = vec![Dynamic::from(200i64), Dynamic::from("OK")];
let search = vec![Dynamic::from(200i64), Dynamic::from(404i64)];
assert!(starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_string_number_conversion() {
let arr = vec![Dynamic::from(500i64)];
let search = vec![Dynamic::from("500")];
assert!(starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_empty_array() {
let arr = Array::new();
let search = vec![Dynamic::from("test")];
assert!(!starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_empty_search() {
let arr = vec![Dynamic::from("test")];
let search = Array::new();
assert!(!starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_only_first_element() {
let arr = vec![
Dynamic::from("INFO"),
Dynamic::from("ERROR"), ];
let search = vec![Dynamic::from("ERROR")];
assert!(!starts_with_any_array(arr, search));
}
#[test]
fn test_starts_with_any_mixed_types() {
let arr = vec![Dynamic::from(true), Dynamic::from("second")];
let search = vec![Dynamic::from("true"), Dynamic::from("false")];
assert!(starts_with_any_array(arr, search));
}
#[test]
fn test_min_array_numbers() {
let arr = vec![
Dynamic::from(3i64),
Dynamic::from(1i64),
Dynamic::from(4i64),
Dynamic::from(1i64),
Dynamic::from(5i64),
];
let result = min_array(arr);
assert_eq!(result.as_int().unwrap(), 1i64);
}
#[test]
fn test_max_array_numbers() {
let arr = vec![
Dynamic::from(3i64),
Dynamic::from(1i64),
Dynamic::from(4i64),
Dynamic::from(1i64),
Dynamic::from(5i64),
];
let result = max_array(arr);
assert_eq!(result.as_int().unwrap(), 5i64);
}
#[test]
fn test_min_array_floats() {
let arr = vec![
Dynamic::from(std::f64::consts::PI),
Dynamic::from(2.71),
Dynamic::from(1.41),
];
let result = min_array(arr);
assert_eq!(result.as_float().unwrap(), 1.41);
}
#[test]
fn test_max_array_floats() {
let arr = vec![
Dynamic::from(std::f64::consts::PI),
Dynamic::from(2.71),
Dynamic::from(1.41),
];
let result = max_array(arr);
assert!((result.as_float().unwrap() - std::f64::consts::PI).abs() < 1e-10);
}
#[test]
fn test_min_array_strings() {
let arr = vec![
Dynamic::from("banana"),
Dynamic::from("apple"),
Dynamic::from("cherry"),
];
let result = min_array(arr);
assert_eq!(result.to_string(), "apple");
}
#[test]
fn test_max_array_strings() {
let arr = vec![
Dynamic::from("banana"),
Dynamic::from("apple"),
Dynamic::from("cherry"),
];
let result = max_array(arr);
assert_eq!(result.to_string(), "cherry");
}
#[test]
fn test_min_array_numeric_strings() {
let arr = vec![
Dynamic::from("10"),
Dynamic::from("2"),
Dynamic::from("100"),
];
let result = min_array(arr);
assert_eq!(result.to_string(), "10");
}
#[test]
fn test_max_array_numeric_strings() {
let arr = vec![
Dynamic::from("10"),
Dynamic::from("2"),
Dynamic::from("100"),
];
let result = max_array(arr);
assert_eq!(result.to_string(), "2");
}
#[test]
fn test_min_array_booleans() {
let arr = vec![Dynamic::from(true), Dynamic::from(false)];
let result = min_array(arr);
assert_eq!(result.to_string(), "false");
}
#[test]
fn test_max_array_booleans() {
let arr = vec![Dynamic::from(true), Dynamic::from(false)];
let result = max_array(arr);
assert_eq!(result.to_string(), "true");
}
#[test]
fn test_min_array_mixed_types() {
let arr = vec![Dynamic::from(1i64), Dynamic::from("apple")];
let result = min_array(arr);
assert!(result.is_unit()); }
#[test]
fn test_max_array_mixed_types() {
let arr = vec![Dynamic::from(1i64), Dynamic::from("apple")];
let result = max_array(arr);
assert!(result.is_unit()); }
#[test]
fn test_min_array_empty() {
let arr = Array::new();
let result = min_array(arr);
assert!(result.is_unit()); }
#[test]
fn test_max_array_empty() {
let arr = Array::new();
let result = max_array(arr);
assert!(result.is_unit()); }
#[test]
fn test_min_array_single_element() {
let arr = vec![Dynamic::from(42i64)];
let result = min_array(arr);
assert_eq!(result.as_int().unwrap(), 42i64);
}
#[test]
fn test_max_array_single_element() {
let arr = vec![Dynamic::from(42i64)];
let result = max_array(arr);
assert_eq!(result.as_int().unwrap(), 42i64);
}
#[test]
fn test_determine_array_type() {
let numeric_arr = vec![Dynamic::from(1i64), Dynamic::from(2.5)];
assert_eq!(determine_array_type(&numeric_arr), ArrayType::Numeric);
let string_arr = vec![Dynamic::from("hello"), Dynamic::from("world")];
assert_eq!(determine_array_type(&string_arr), ArrayType::String);
let mixed_arr = vec![Dynamic::from(1i64), Dynamic::from("hello")];
assert_eq!(determine_array_type(&mixed_arr), ArrayType::Mixed);
let empty_arr = Array::new();
assert_eq!(determine_array_type(&empty_arr), ArrayType::Empty);
let numeric_str_arr = vec![Dynamic::from("10"), Dynamic::from("20")];
assert_eq!(determine_array_type(&numeric_str_arr), ArrayType::String);
let bool_arr = vec![Dynamic::from(true), Dynamic::from(false)];
assert_eq!(determine_array_type(&bool_arr), ArrayType::String);
let mixed_num_str = vec![Dynamic::from(1i64), Dynamic::from("10")];
assert_eq!(determine_array_type(&mixed_num_str), ArrayType::Mixed);
}
#[test]
fn test_min_max_with_mixed_numeric_types() {
let arr = vec![Dynamic::from(3i64), Dynamic::from(1.5), Dynamic::from(4i64)];
let min_result = min_array(arr.clone());
let max_result = max_array(arr);
assert_eq!(min_result.as_float().unwrap(), 1.5);
assert_eq!(max_result.as_int().unwrap(), 4i64);
}
#[test]
fn test_min_max_mixed_numbers_and_strings_rejected() {
let arr = vec![Dynamic::from(3i64), Dynamic::from("2")];
let min_result = min_array(arr.clone());
let max_result = max_array(arr);
assert!(min_result.is_unit());
assert!(max_result.is_unit());
}
fn array_to_ints(arr: Array) -> Vec<i64> {
arr.into_iter().map(|d| d.as_int().unwrap()).collect()
}
#[test]
fn test_slice_array_basic_range() {
let arr = vec![
Dynamic::from(0i64),
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
Dynamic::from(4i64),
];
let result = slice_array(arr, "1:4");
assert_eq!(array_to_ints(result), vec![1, 2, 3]);
}
#[test]
fn test_slice_array_negative_indices_and_step() {
let arr = vec![
Dynamic::from(0i64),
Dynamic::from(1i64),
Dynamic::from(2i64),
Dynamic::from(3i64),
Dynamic::from(4i64),
];
let result = slice_array(arr.clone(), "-4:-1");
assert_eq!(array_to_ints(result), vec![1, 2, 3]);
let reversed = slice_array(arr, "3:0:-1");
assert_eq!(array_to_ints(reversed), vec![3, 2, 1]);
}
#[test]
fn test_slice_array_defaults_and_zero_step() {
let arr = vec![
Dynamic::from(10i64),
Dynamic::from(20i64),
Dynamic::from(30i64),
];
let no_spec = slice_array(arr.clone(), "");
assert_eq!(array_to_ints(no_spec), vec![10, 20, 30]);
let zero_step = slice_array(arr, "0:2:0");
assert!(zero_step.is_empty());
}
}