use base64::{Engine as Base64Engine, engine::general_purpose};
use chrono::Datelike;
use chrono::TimeZone;
use chrono::Timelike;
use chrono::{DateTime, Utc};
use regex::Regex;
use rhai::{Engine, EvalAltResult};
use valu3::prelude::JsonMode;
use valu3::value::Value;
pub fn build_functions() -> Engine {
let mut engine = Engine::new();
engine.register_fn("today", || {
let now = Utc::now();
Utc.with_ymd_and_hms(now.year(), now.month(), now.day(), 0, 0, 0)
.unwrap()
.timestamp() as i64
});
engine.register_fn("now", || Utc::now().timestamp() as i64);
engine.register_fn("format", |ts: i64, fmt: &str| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.format(fmt).to_string()
} else {
String::new()
}
});
engine.register_fn("diff", |ts1: i64, ts2: i64| ts1 - ts2);
engine.register_fn("add_days", |ts: i64, n: i64| ts + n * 86400);
engine.register_fn("weekday", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.weekday().num_days_from_sunday() as i64
} else {
-1i64
}
});
engine.register_fn("year", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.year() as i64
} else {
0i64
}
});
engine.register_fn("month", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.month() as i64
} else {
0i64
}
});
engine.register_fn("day", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.day() as i64
} else {
0i64
}
});
engine.register_fn("hour", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.hour() as i64
} else {
0i64
}
});
engine.register_fn("minute", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.minute() as i64
} else {
0i64
}
});
engine.register_fn("second", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.second() as i64
} else {
0i64
}
});
engine.register_fn("add_seconds", |ts: i64, n: i64| ts + n);
engine.register_fn("add_minutes", |ts: i64, n: i64| ts + n * 60);
engine.register_fn("add_hours", |ts: i64, n: i64| ts + n * 3600);
engine.register_fn("sub_seconds", |ts: i64, n: i64| ts - n);
engine.register_fn("sub_minutes", |ts: i64, n: i64| ts - n * 60);
engine.register_fn("from_iso", |iso: &str| {
DateTime::parse_from_rfc3339(iso)
.map(|dt| dt.timestamp() as i64)
.unwrap_or(0)
});
engine.register_fn("to_iso", |ts: i64| {
if let Some(dt) = DateTime::<Utc>::from_timestamp(ts, 0) {
dt.to_rfc3339()
} else {
String::new()
}
});
engine.register_fn("is_null", |x: rhai::Dynamic| x.is_unit());
engine.register_fn("is_not_null", |x: rhai::Dynamic| !x.is_unit());
engine.register_fn("merge", |x: rhai::Dynamic, y: rhai::Dynamic| {
if let (Some(mut x), Some(y)) = (x.try_cast::<rhai::Map>(), y.try_cast::<rhai::Map>()) {
x.extend(y.into_iter());
rhai::Dynamic::from(x)
} else {
rhai::Dynamic::UNIT
}
});
engine.register_fn("is_empty", |x: rhai::Dynamic| {
if x.is_unit() {
true
} else if let Some(s) = x.clone().try_cast::<String>() {
s.trim().is_empty()
} else if let Some(s) = x.clone().try_cast::<rhai::ImmutableString>() {
s.trim().is_empty()
} else {
false
}
});
engine.register_fn("is_empty", |s: &str| s.trim().is_empty());
engine.register_fn("search", |s: &str, pattern: &str| {
Regex::new(pattern)
.map(|re| re.is_match(s))
.unwrap_or(false)
});
engine.register_fn("starts_with", |s: &str, prefix: &str| s.starts_with(prefix));
engine.register_fn("replace", |s: &str, target: &str, replacement: &str| {
s.replace(target, replacement)
});
engine.register_fn("slice", |s: &str, start: i64, end: i64| {
let len = s.chars().count() as i64;
let start = if start < 0 { 0 } else { start };
let end = if end > len { len } else { end };
if start >= end || start >= len {
String::new()
} else {
s.chars()
.skip(start as usize)
.take((end - start) as usize)
.collect()
}
});
engine.register_fn("slice", |s: &str, start: i64| {
let len = s.chars().count() as i64;
let start = if start < 0 {
let abs_start = start.abs();
if abs_start > len { 0 } else { len - abs_start }
} else {
start
};
if start >= len {
String::new()
} else {
s.chars().skip(start as usize).collect()
}
});
engine.register_fn("capitalize", |s: &str| {
if s.is_empty() {
String::new()
} else {
let mut chars: Vec<char> = s.chars().collect();
chars[0] = chars[0].to_uppercase().next().unwrap_or(chars[0]);
chars.iter().collect()
}
});
fn split_words(s: &str) -> Vec<String> {
let mut words = Vec::new();
let normalized = s.replace("_", " ").replace("-", " ");
let camel_regex = Regex::new(r"([a-z])([A-Z])").unwrap();
let spaced = camel_regex.replace_all(&normalized, "$1 $2");
let word_regex = Regex::new(r"[a-zA-Z]+|[0-9]+").unwrap();
for word_match in word_regex.find_iter(&spaced) {
let word = word_match.as_str().to_lowercase();
if !word.is_empty() {
words.push(word);
}
}
if words.is_empty() && !s.trim().is_empty() {
words.push(s.trim().to_lowercase());
}
words
}
engine.register_fn("to_snake_case", |s: &str| split_words(s).join("_"));
engine.register_fn("to_camel_case", |s: &str| {
let words = split_words(s);
if words.is_empty() {
return String::new();
}
let mut result = words[0].clone();
for word in words.iter().skip(1) {
if !word.is_empty() {
let mut chars: Vec<char> = word.chars().collect();
chars[0] = chars[0].to_uppercase().next().unwrap_or(chars[0]);
result.push_str(&chars.iter().collect::<String>());
}
}
result
});
engine.register_fn("to_kebab_case", |s: &str| split_words(s).join("-"));
engine.register_fn("to_url_encode", |s: &str| {
s.bytes()
.map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
(b as char).to_string()
}
b' ' => "+".to_string(),
_ => format!("%{:02X}", b),
})
.collect::<String>()
});
engine.register_fn("to_base64", |s: &str| {
general_purpose::STANDARD.encode(s.as_bytes())
});
engine.register_fn("base64_to_utf8", |s: &str| -> String {
let mut padded_input = s.to_string();
let remainder = padded_input.len() % 4;
if remainder != 0 {
let padding_needed = 4 - remainder;
for _ in 0..padding_needed {
padded_input.push('=');
}
}
match general_purpose::STANDARD.decode(&padded_input) {
Ok(bytes) => {
match String::from_utf8(bytes) {
Ok(decoded) => decoded,
Err(_) => String::new(), }
}
Err(_) => String::new(), }
});
engine.register_fn("url_decode", |s: &str| -> String {
let mut result = Vec::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'+' => result.push(b' '), '%' => {
let hex1 = chars.next();
let hex2 = chars.next();
if let (Some(h1), Some(h2)) = (hex1, hex2) {
let hex_str = format!("{}{}", h1, h2);
if let Ok(byte) = u8::from_str_radix(&hex_str, 16) {
result.push(byte);
} else {
result.extend(format!("%{}{}", h1, h2).bytes());
}
} else {
result.push(b'%');
if let Some(h1) = hex1 {
result.extend(h1.to_string().bytes());
}
if let Some(h2) = hex2 {
result.extend(h2.to_string().bytes());
}
}
}
_ => {
result.extend(ch.to_string().bytes());
}
}
}
match String::from_utf8(result) {
Ok(decoded) => decoded,
Err(_) => String::new(), }
});
engine.register_fn("parse", |s: &str| -> rhai::Dynamic {
match Value::json_to_value(s) {
Ok(value) => {
match value {
Value::Null => rhai::Dynamic::UNIT,
Value::Boolean(b) => rhai::Dynamic::from(b),
Value::Number(n) => {
let num_str = n.to_string();
if num_str.contains('.') {
num_str
.parse::<f64>()
.map(|f| rhai::Dynamic::from(f))
.unwrap_or_else(|_| rhai::Dynamic::from(num_str))
} else {
num_str
.parse::<i64>()
.map(|i| rhai::Dynamic::from(i))
.unwrap_or_else(|_| rhai::Dynamic::from(num_str))
}
}
Value::String(s) => rhai::Dynamic::from(s.to_string()),
Value::Array(_) => {
let json_str = value.to_string();
match rhai::serde::to_dynamic(&value) {
Ok(dynamic_val) => dynamic_val,
Err(_) => rhai::Dynamic::from(json_str),
}
}
Value::Object(_) => {
let json_str = value.to_string();
match rhai::serde::to_dynamic(&value) {
Ok(dynamic_val) => dynamic_val,
Err(_) => rhai::Dynamic::from(json_str),
}
}
_ => rhai::Dynamic::from(value.to_string()),
}
}
Err(_) => rhai::Dynamic::UNIT,
}
});
engine.register_fn("to_json", |value: rhai::Dynamic| -> rhai::Dynamic {
let value: Value = match rhai::serde::from_dynamic(&value) {
Ok(serde_value) => serde_value,
Err(_) => Value::Undefined,
};
let json = value.to_json(JsonMode::Inline);
rhai::Dynamic::from(json)
});
engine.register_fn("__spread_object", |objects: rhai::Array| -> rhai::Dynamic {
let mut result_map = rhai::Map::new();
for obj in objects {
if let Some(map) = obj.try_cast::<rhai::Map>() {
for (key, value) in map {
result_map.insert(key, value);
}
}
}
rhai::Dynamic::from(result_map)
});
engine.register_fn("__spread_array", |arrays: rhai::Array| -> rhai::Dynamic {
let mut result_array = rhai::Array::new();
for arr in arrays {
if let Some(array) = arr.clone().try_cast::<rhai::Array>() {
result_array.extend(array);
} else {
result_array.push(arr);
}
}
rhai::Dynamic::from(result_array)
});
engine.register_fn("uuid", |uuid_type: &str| -> String {
match uuid_type {
"v4" => uuid::Uuid::new_v4().to_string(),
"v6" => {
let node_id = [0u8; 6]; uuid::Uuid::new_v6(
uuid::Timestamp::now(uuid::timestamp::context::NoContext),
&node_id,
)
.to_string()
}
"v7" => uuid::Uuid::new_v7(uuid::Timestamp::now(uuid::timestamp::context::NoContext))
.to_string(),
_ => uuid::Uuid::new_v4().to_string(), }
});
engine.register_fn("uuid", |uuid_type: &str, hash: &str| -> String {
match uuid_type {
"v3" => {
let namespace = uuid::Uuid::NAMESPACE_DNS;
uuid::Uuid::new_v3(&namespace, hash.as_bytes()).to_string()
}
"v5" => {
let namespace = uuid::Uuid::NAMESPACE_DNS;
uuid::Uuid::new_v5(&namespace, hash.as_bytes()).to_string()
}
_ => uuid::Uuid::new_v4().to_string(), }
});
engine.register_fn("uuid", || -> String { uuid::Uuid::new_v4().to_string() });
engine.register_fn("is_array", |x: rhai::Dynamic| x.is_array());
engine.register_fn("is_object", |x: rhai::Dynamic| x.is_map());
engine.register_fn("is_string", |x: rhai::Dynamic| x.is_string());
engine.register_fn("is_number", |x: rhai::Dynamic| x.is_int() || x.is_float());
engine.register_fn("is_boolean", |x: rhai::Dynamic| x.is_bool());
engine.register_fn("is_datetime", |x: rhai::Dynamic| {
if let Some(s) = x.clone().try_cast::<String>() {
DateTime::parse_from_rfc3339(&s).is_ok()
} else {
false
}
});
engine.register_fn("is_float", |x: rhai::Dynamic| x.is_float());
engine.register_fn("is_int", |x: rhai::Dynamic| x.is_int());
engine.register_fn("contains_key", |map: rhai::Map, key: &str| -> bool {
map.contains_key(key)
});
engine.register_fn(
"contains_key",
|map: rhai::Map, key: rhai::ImmutableString| -> bool { map.contains_key(key.as_str()) },
);
engine.register_fn("like", |s: &str, pattern: &str| {
let regex_pattern = format!(
"^{}$",
regex::escape(pattern).replace("%", ".*").replace("_", ".")
);
regex::Regex::new(®ex_pattern).unwrap().is_match(s)
});
engine.register_fn("ilike", |s: &str, pattern: &str| {
let regex_pattern = format!(
"^{}$",
regex::escape(pattern).replace("%", ".*").replace("_", ".")
);
regex::RegexBuilder::new(®ex_pattern)
.case_insensitive(true)
.build()
.unwrap()
.is_match(s)
});
engine.register_fn("not_like", |s: &str, pattern: &str| {
let regex_pattern = format!(
"^{}$",
regex::escape(pattern).replace("%", ".*").replace("_", ".")
);
!regex::Regex::new(®ex_pattern).unwrap().is_match(s)
});
engine.register_fn("not_ilike", |s: &str, pattern: &str| {
let regex_pattern = format!(
"^{}$",
regex::escape(pattern).replace("%", ".*").replace("_", ".")
);
!regex::RegexBuilder::new(®ex_pattern)
.case_insensitive(true)
.build()
.unwrap()
.is_match(s)
});
engine.register_fn("contains", |s: &str, substring: &str| s.contains(substring));
engine.register_fn(
"some",
move |context: rhai::NativeCallContext,
arr: rhai::Array,
func: rhai::FnPtr|
-> Result<bool, Box<EvalAltResult>> {
for item in arr {
let result = func.call_within_context::<bool>(&context, (item,))?;
if result {
return Ok(true);
}
}
Ok(false)
},
);
match engine.register_custom_syntax(
["when", "$expr$", "then", "$expr$", "else", "$expr$"],
false,
|context, inputs| match context.eval_expression_tree(&inputs[0])?.as_bool() {
Ok(true) => context.eval_expression_tree(&inputs[1]),
Ok(false) => context.eval_expression_tree(&inputs[2]),
Err(typ) => Err(Box::new(EvalAltResult::ErrorMismatchDataType(
"bool".to_string(),
typ.to_string(),
inputs[0].position(),
))),
},
) {
Ok(engine) => engine,
Err(_) => {
panic!("Error on register custom syntax when ternary");
}
};
match engine.register_custom_syntax(
["it", "$expr$", "?", "$expr$", ":", "$expr$"],
false,
|context, inputs| match context.eval_expression_tree(&inputs[0])?.as_bool() {
Ok(true) => context.eval_expression_tree(&inputs[1]),
Ok(false) => context.eval_expression_tree(&inputs[2]),
Err(typ) => Err(Box::new(EvalAltResult::ErrorMismatchDataType(
"bool".to_string(),
typ.to_string(),
inputs[0].position(),
))),
},
) {
Ok(engine) => engine,
Err(_) => {
panic!("Error on register custom syntax it ternary");
}
};
engine
}
#[cfg(test)]
mod tests {
#[test]
fn test_time_extras() {
let engine = build_functions();
let today: i64 = engine.eval("today()").unwrap();
let now: i64 = engine.eval("now()").unwrap();
assert!(now >= today);
assert!(now - today < 86400);
let iso: String = engine.eval("to_iso(1692362096)").unwrap();
assert_eq!(iso, "2023-08-18T12:34:56+00:00");
let custom: String = engine
.eval(r#"format(1692362096, "%d/%m/%Y %H:%M:%S")"#)
.unwrap();
assert_eq!(custom, "18/08/2023 12:34:56");
assert_eq!(engine.eval::<i64>("diff(100, 50)").unwrap(), 50);
assert_eq!(
engine.eval::<i64>("add_days(1000, 2)").unwrap(),
1000 + 2 * 86400
);
let wd: i64 = engine.eval("weekday(1692362096)").unwrap();
assert_eq!(wd, 5);
assert_eq!(engine.eval::<i64>("year(1692362096)").unwrap(), 2023);
assert_eq!(engine.eval::<i64>("month(1692362096)").unwrap(), 8);
assert_eq!(engine.eval::<i64>("day(1692362096)").unwrap(), 18);
assert_eq!(engine.eval::<i64>("hour(1692362096)").unwrap(), 12);
assert_eq!(engine.eval::<i64>("minute(1692362096)").unwrap(), 34);
assert_eq!(engine.eval::<i64>("second(1692362096)").unwrap(), 56);
}
use super::*;
#[test]
fn test_time_functions() {
let engine = build_functions();
let now: i64 = engine.eval("now()").unwrap();
let sys_now = Utc::now().timestamp();
assert!((now - sys_now).abs() < 5);
assert_eq!(engine.eval::<i64>("add_seconds(1000, 10)").unwrap(), 1010);
assert_eq!(engine.eval::<i64>("add_minutes(1000, 2)").unwrap(), 1120);
assert_eq!(engine.eval::<i64>("add_hours(1000, 1)").unwrap(), 4600);
assert_eq!(engine.eval::<i64>("sub_seconds(1000, 10)").unwrap(), 990);
assert_eq!(engine.eval::<i64>("sub_minutes(1000, 2)").unwrap(), 880);
let ts: i64 = engine.eval(r#"from_iso("2023-08-18T12:34:56Z")"#).unwrap();
assert_eq!(ts, 1692362096);
let iso: String = engine.eval("to_iso(1692362096)").unwrap();
assert!(iso.starts_with("2023-08-18T12:34:56"));
}
#[test]
fn test_search_function() {
let engine = build_functions();
let result: bool = engine.eval(r#""meu texto".search("texto")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""meu texto".search("^meu")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""meu texto".search("texto$")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""meu texto".search("abc")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#""meu texto".search("[")"#).unwrap();
assert!(!result);
}
#[test]
fn test_starts_with_function() {
let engine = build_functions();
let result: bool = engine
.eval(r#""Bearer token123".starts_with("Bearer")"#)
.unwrap();
assert!(result);
let result: bool = engine
.eval(r#""Bearer token123".starts_with("Bearer ")"#)
.unwrap();
assert!(result);
let result: bool = engine
.eval(r#""Basic auth123".starts_with("Bearer")"#)
.unwrap();
assert!(!result);
let result: bool = engine.eval(r#""qualquer texto".starts_with("")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""abc".starts_with("abcdef")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#""Bearer".starts_with("bearer")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#""@user123".starts_with("@")"#).unwrap();
assert!(result);
}
#[test]
fn test_replace_function() {
let engine = build_functions();
let result: String = engine
.eval(r#""meu texto".replace("texto", "valor")"#)
.unwrap();
assert_eq!(result, "meu valor");
let result: String = engine
.eval(r#""meu texto".replace("abc", "valor")"#)
.unwrap();
assert_eq!(result, "meu texto");
let result: String = engine.eval(r#""abc abc abc".replace("abc", "x")"#).unwrap();
assert_eq!(result, "x x x");
let result: String = engine.eval(r#""meu texto".replace("texto", "")"#).unwrap();
assert_eq!(result, "meu ");
}
#[test]
fn test_is_null_function() {
let engine = build_functions();
let result: bool = engine.eval(r#"is_null(())"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_null(123)"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_null("texto")"#).unwrap();
assert!(!result);
}
#[test]
fn test_is_not_null_function() {
let engine = build_functions();
let result: bool = engine.eval(r#"is_not_null(())"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_not_null(123)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_not_null("texto")"#).unwrap();
assert!(result);
}
#[test]
fn test_is_empty_function() {
let engine = build_functions();
let result: bool = engine.eval(r#"is_empty("")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_empty(" ")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_empty("abc")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_empty(())"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""".is_empty()"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"" ".is_empty()"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""abc".is_empty()"#).unwrap();
assert!(!result);
}
#[test]
fn test_merge_function() {
let engine = build_functions();
let result: rhai::Dynamic = engine
.eval(r#"merge(#{ "a": 1, "b": 2 },#{ "b": 3, "c": 4 })"#)
.unwrap();
let map: rhai::Map = result.try_cast().unwrap();
assert_eq!(map.get("a").unwrap().as_int().unwrap(), 1);
assert_eq!(map.get("b").unwrap().as_int().unwrap(), 3);
assert_eq!(map.get("c").unwrap().as_int().unwrap(), 4);
}
#[test]
fn test_slice_function() {
let engine = build_functions();
let result: String = engine.eval(r#""abcdef".slice(1, 4)"#).unwrap();
assert_eq!(result, "bcd");
let result: String = engine.eval(r#""abcdef".slice(-2, 3)"#).unwrap();
assert_eq!(result, "abc");
let result: String = engine.eval(r#""abcdef".slice(2, 10)"#).unwrap();
assert_eq!(result, "cdef");
let result: String = engine.eval(r#""abcdef".slice(4, 2)"#).unwrap();
assert_eq!(result, "");
let result: String = engine.eval(r#""abcdef".slice(10, 12)"#).unwrap();
assert_eq!(result, "");
let result: String = engine.eval(r#""abcdef".slice(0,3)"#).unwrap();
assert_eq!(result, "abc");
let result: String = engine.eval(r#""abcdef".slice(3)"#).unwrap();
assert_eq!(result, "def");
let result: String = engine.eval(r#""abcdef".slice(-2)"#).unwrap();
assert_eq!(result, "ef");
}
#[test]
fn test_capitalize_function() {
let engine = build_functions();
let result: String = engine.eval(r#""exemplo".capitalize()"#).unwrap();
assert_eq!(result, "Exemplo");
let result: String = engine.eval(r#""a".capitalize()"#).unwrap();
assert_eq!(result, "A");
let result: String = engine.eval(r#""".capitalize()"#).unwrap();
assert_eq!(result, "");
let result: String = engine.eval(r#""ábc".capitalize()"#).unwrap();
assert_eq!(result, "Ábc");
let result: String = engine
.eval(r#"let a = #{value: "ábc"}; a.value.capitalize()"#)
.unwrap();
assert_eq!(result, "Ábc");
}
#[test]
fn test_to_snake_case() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Meu texto exemplo".to_snake_case()"#)
.unwrap(),
"meu_texto_exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meuTextoExemplo".to_snake_case()"#)
.unwrap(),
"meu_texto_exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""MeuTextoExemplo".to_snake_case()"#)
.unwrap(),
"meu_texto_exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu_texto_exemplo".to_snake_case()"#)
.unwrap(),
"meu_texto_exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu-texto-exemplo".to_snake_case()"#)
.unwrap(),
"meu_texto_exemplo"
);
}
#[test]
fn test_to_camel_case() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Meu texto exemplo".to_camel_case()"#)
.unwrap(),
"meuTextoExemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu_texto_exemplo".to_camel_case()"#)
.unwrap(),
"meuTextoExemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu-texto-exemplo".to_camel_case()"#)
.unwrap(),
"meuTextoExemplo"
);
assert_eq!(
engine
.eval::<String>(r#""MeuTextoExemplo".to_camel_case()"#)
.unwrap(),
"meuTextoExemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meuTextoExemplo".to_camel_case()"#)
.unwrap(),
"meuTextoExemplo"
);
}
#[test]
fn test_to_kebab_case() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Meu texto exemplo".to_kebab_case()"#)
.unwrap(),
"meu-texto-exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meuTextoExemplo".to_kebab_case()"#)
.unwrap(),
"meu-texto-exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu_texto_exemplo".to_kebab_case()"#)
.unwrap(),
"meu-texto-exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""MeuTextoExemplo".to_kebab_case()"#)
.unwrap(),
"meu-texto-exemplo"
);
assert_eq!(
engine
.eval::<String>(r#""meu-texto-exemplo".to_kebab_case()"#)
.unwrap(),
"meu-texto-exemplo"
);
}
#[test]
fn test_to_url_encode() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Hello World".to_url_encode()"#)
.unwrap(),
"Hello+World"
);
assert_eq!(
engine
.eval::<String>(r#""user@example.com".to_url_encode()"#)
.unwrap(),
"user%40example.com"
);
assert_eq!(
engine
.eval::<String>(r#""abc-123_test.file~".to_url_encode()"#)
.unwrap(),
"abc-123_test.file~"
);
assert_eq!(
engine
.eval::<String>(r#""café & maçã".to_url_encode()"#)
.unwrap(),
"caf%C3%A9+%26+ma%C3%A7%C3%A3"
);
assert_eq!(engine.eval::<String>(r#""".to_url_encode()"#).unwrap(), "");
}
#[test]
fn test_to_base64() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Hello World".to_base64()"#)
.unwrap(),
"SGVsbG8gV29ybGQ="
);
assert_eq!(engine.eval::<String>(r#""".to_base64()"#).unwrap(), "");
assert_eq!(
engine
.eval::<String>(r#""user@example.com".to_base64()"#)
.unwrap(),
"dXNlckBleGFtcGxlLmNvbQ=="
);
assert_eq!(
engine.eval::<String>(r#""café".to_base64()"#).unwrap(),
"Y2Fmw6k="
);
assert_eq!(
engine.eval::<String>(r#""12345".to_base64()"#).unwrap(),
"MTIzNDU="
);
}
#[test]
fn test_when_and_case_ternary() {
let engine = build_functions();
let result: i64 = engine.eval(r#"it true ? 42 : 0"#).unwrap();
assert_eq!(result, 42);
let result: i64 = engine.eval(r#"it false ? 42 : 0"#).unwrap();
assert_eq!(result, 0);
let result: String = engine.eval(r#"it 5 > 3 ? "maior" : "menor""#).unwrap();
assert_eq!(result, "maior");
let result: String = engine
.eval(r#"it "abc".search("b") ? "encontrou" : "não encontrou""#)
.unwrap();
assert_eq!(result, "encontrou");
let result: i64 = engine.eval(r#"when true then 42 else 0"#).unwrap();
assert_eq!(result, 42);
let result: i64 = engine.eval(r#"when false then 42 else 0"#).unwrap();
assert_eq!(result, 0);
let result: String = engine
.eval(r#"when 2 + 2 == 4 then "certo" else "errado""#)
.unwrap();
assert_eq!(result, "certo");
}
#[test]
fn test_base64_to_utf8() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""SGVsbG8gV29ybGQ=".base64_to_utf8()"#)
.unwrap(),
"Hello World"
);
assert_eq!(engine.eval::<String>(r#""".base64_to_utf8()"#).unwrap(), "");
assert_eq!(
engine
.eval::<String>(r#""dXNlckBleGFtcGxlLmNvbQ==".base64_to_utf8()"#)
.unwrap(),
"user@example.com"
);
assert_eq!(
engine
.eval::<String>(r#""Y2Fmw6k=".base64_to_utf8()"#)
.unwrap(),
"café"
);
assert_eq!(
engine
.eval::<String>(r#""MTIzNDU=".base64_to_utf8()"#)
.unwrap(),
"12345"
);
assert_eq!(
engine
.eval::<String>(r#""invalid_base64!@#".base64_to_utf8()"#)
.unwrap(),
""
);
assert_eq!(
engine.eval::<String>(r#""//8=".base64_to_utf8()"#).unwrap(),
""
);
assert_eq!(
engine
.eval::<String>(r#""eyJlbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20ifQ".base64_to_utf8()"#)
.unwrap(),
"{\"email\":\"example@example.com\"}"
);
}
#[test]
fn test_url_decode() {
let engine = build_functions();
assert_eq!(
engine
.eval::<String>(r#""Hello+World".url_decode()"#)
.unwrap(),
"Hello World"
);
assert_eq!(
engine
.eval::<String>(r#""user%40example.com".url_decode()"#)
.unwrap(),
"user@example.com"
);
assert_eq!(
engine
.eval::<String>(r#""abc-123_test.file~".url_decode()"#)
.unwrap(),
"abc-123_test.file~"
);
assert_eq!(
engine
.eval::<String>(r#""caf%C3%A9+%26+ma%C3%A7%C3%A3".url_decode()"#)
.unwrap(),
"café & maçã"
);
assert_eq!(engine.eval::<String>(r#""".url_decode()"#).unwrap(), "");
assert_eq!(
engine.eval::<String>(r#""%ZZ".url_decode()"#).unwrap(),
"%ZZ"
);
assert_eq!(
engine.eval::<String>(r#""test%".url_decode()"#).unwrap(),
"test%"
);
assert_eq!(
engine.eval::<String>(r#""test%2".url_decode()"#).unwrap(),
"test%2"
);
assert_eq!(
engine
.eval::<String>(r#""Ol%C3%A1+mundo%21+Como+vai%3F".url_decode()"#)
.unwrap(),
"Olá mundo! Como vai?"
);
}
#[test]
fn test_parse() {
let engine = build_functions();
let result: rhai::Dynamic = engine
.eval(r#""{\"name\":\"João\",\"age\":30}".parse()"#)
.unwrap();
if let Some(map) = result.clone().try_cast::<rhai::Map>() {
assert!(map.contains_key("name"));
assert!(map.contains_key("age"));
if let Some(name) = map.get("name") {
if let Some(name_str) = name.clone().try_cast::<String>() {
assert_eq!(name_str, "João");
}
}
if let Some(age) = map.get("age") {
if let Ok(age_val) = age.as_int() {
assert_eq!(age_val, 30);
}
}
} else {
panic!("Esperado um Map, mas recebeu: {:?}", result.type_name());
}
let result: rhai::Dynamic = engine.eval(r#""[1, 2, 3, \"test\"]".parse()"#).unwrap();
if let Some(array) = result.clone().try_cast::<rhai::Array>() {
assert_eq!(array.len(), 4);
assert_eq!(array[0].as_int().unwrap(), 1);
assert_eq!(array[1].as_int().unwrap(), 2);
assert_eq!(array[2].as_int().unwrap(), 3);
assert_eq!(array[3].clone().try_cast::<String>().unwrap(), "test");
} else {
panic!("Esperado um Array, mas recebeu: {:?}", result.type_name());
}
let result: String = engine.eval(r#""\"hello world\"".parse()"#).unwrap();
assert_eq!(result, "hello world");
let result: i64 = engine.eval(r#""42".parse()"#).unwrap();
assert_eq!(result, 42);
let result: f64 = engine.eval(r#""3.14".parse()"#).unwrap();
assert_eq!(result, 3.14);
let result: bool = engine.eval(r#""true".parse()"#).unwrap();
assert_eq!(result, true);
let result: bool = engine.eval(r#""false".parse()"#).unwrap();
assert_eq!(result, false);
let result: rhai::Dynamic = engine.eval(r#""null".parse()"#).unwrap();
assert!(result.is_unit());
let result: rhai::Dynamic = engine.eval(r#""{invalid json}".parse()"#).unwrap();
assert!(result.is_unit());
let result: rhai::Dynamic = engine.eval(r#""".parse()"#).unwrap();
assert!(result.is_unit());
}
#[test]
fn test_parse_complex_structures() {
let engine = build_functions();
let result: rhai::Dynamic = engine
.eval(r#""{\"user\":{\"name\":\"Maria\",\"age\":25},\"active\":true}".parse()"#)
.unwrap();
if let Some(map) = result.clone().try_cast::<rhai::Map>() {
assert!(map.contains_key("user"));
assert!(map.contains_key("active"));
if let Some(user) = map.get("user") {
if let Some(user_map) = user.clone().try_cast::<rhai::Map>() {
assert!(user_map.contains_key("name"));
assert!(user_map.contains_key("age"));
}
}
} else {
panic!("Esperado um Map para objeto aninhado");
}
let result: rhai::Dynamic = engine
.eval(r#""[{\"id\":1,\"name\":\"João\"},{\"id\":2,\"name\":\"Maria\"}]".parse()"#)
.unwrap();
if let Some(array) = result.clone().try_cast::<rhai::Array>() {
assert_eq!(array.len(), 2);
if let Some(first_obj) = array.get(0) {
if let Some(obj_map) = first_obj.clone().try_cast::<rhai::Map>() {
assert!(obj_map.contains_key("id"));
assert!(obj_map.contains_key("name"));
}
}
} else {
panic!("Esperado um Array de objetos");
}
}
#[test]
fn test_base64_to_utf8_and_parse() {
let engine = build_functions();
let result: String = engine
.eval(
r#""eyJlbWFpbCI6ImV4YW1wbGVAZXhhbXBsZS5jb20ifQ==".base64_to_utf8().parse().email"#,
)
.unwrap();
assert_eq!(result, "example@example.com");
}
#[test]
fn test_to_json() {
let engine = build_functions();
let result: String = engine.eval(r#""hello world".to_json()"#).unwrap();
assert_eq!(result, "\"hello world\"");
let result: String = engine.eval(r#"42.to_json()"#).unwrap();
assert_eq!(result, "42");
let result: String = engine.eval(r#"3.14.to_json()"#).unwrap();
assert_eq!(result, "3.14");
let result: String = engine.eval(r#"true.to_json()"#).unwrap();
assert_eq!(result, "true");
let result: String = engine.eval(r#"false.to_json()"#).unwrap();
assert_eq!(result, "false");
let result: String = engine.eval(r#"().to_json()"#).unwrap();
assert_eq!(result, "null");
let result: String = engine
.eval(r#"#{name: "João", age: 30}.to_json()"#)
.unwrap();
assert!(result.contains("\"name\":\"João\""));
assert!(result.contains("\"age\":30"));
assert!(result.starts_with("{"));
assert!(result.ends_with("}"));
let result: String = engine.eval(r#"[1, 2, "test", true].to_json()"#).unwrap();
assert_eq!(result, "[1,2,\"test\",true]");
let result: String = engine.eval(r#"[].to_json()"#).unwrap();
assert_eq!(result, "[]");
let result: String = engine.eval(r#"#{}.to_json()"#).unwrap();
assert_eq!(result, "{}");
let result: String = engine.eval(r#""He said \"hello\"".to_json()"#).unwrap();
assert_eq!(result, "\"He said \\\"hello\\\"\"");
}
#[test]
fn test_uuid() {
let engine = build_functions();
let uuid_v4: String = engine.eval(r#"uuid("v4")"#).unwrap();
assert_eq!(uuid_v4.len(), 36);
let uuid_v6: String = engine.eval(r#"uuid("v6")"#).unwrap();
assert_eq!(uuid_v6.len(), 36);
let uuid_v7: String = engine.eval(r#"uuid("v7")"#).unwrap();
assert_eq!(uuid_v7.len(), 36);
let uuid_v3: String = engine.eval(r#"uuid("v3", "example.com")"#).unwrap();
assert_eq!(uuid_v3.len(), 36);
let uuid_v5: String = engine.eval(r#"uuid("v5", "example.com")"#).unwrap();
assert_eq!(uuid_v5.len(), 36);
}
#[test]
fn test_is() {
let engine = build_functions();
let result: bool = engine.eval(r#"is_array([1, 2, 3])"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_array(123)"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_object(#{a:1, b:2})"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_object("string")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_string("hello")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_string(123)"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_number(123)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_number(3.14)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_number("string")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_boolean(true)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_boolean(0)"#).unwrap();
assert!(!result);
let result: bool = engine
.eval(r#"is_datetime("2023-08-18T12:34:56Z")"#)
.unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_datetime("invalid date")"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_float(3.14)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_float(123)"#).unwrap();
assert!(!result);
let result: bool = engine.eval(r#"is_int(123)"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#"is_int(3.14)"#).unwrap();
assert!(!result);
}
#[test]
fn test_some() {
let engine = build_functions();
let result: bool = engine.eval(r#"[1, 2, 3].some(|x| x > 2)"#).unwrap();
assert!(result);
let result: bool = engine
.eval(r#"[#{a:1}, #{b:2}, #{c:3}].some(|obj| obj.contains_key("b"))"#)
.unwrap();
assert!(result);
}
#[test]
fn test_likes() {
let engine = build_functions();
let result: bool = engine.eval(r#""hello world".like("h%o w%ld")"#).unwrap();
assert!(result);
let result: bool = engine.eval(r#""hello world".ilike("H%O W%LD")"#).unwrap();
assert!(result);
let result: bool = engine
.eval(r#""hello world".not_like("H%X W%LD")"#)
.unwrap();
assert!(result);
let result: bool = engine
.eval(r#""hello world".not_ilike("H%X W%LD")"#)
.unwrap();
assert!(result);
}
#[test]
fn test_contains_string() {
let engine = build_functions();
let result: bool = engine
.eval(r#""The quick brown fox".contains("brown")"#)
.unwrap();
assert!(result);
}
}