#![allow(clippy::result_large_err)]
use std::collections::HashMap;
use std::rc::Rc;
use qlexpress::exception::error_codes;
use qlexpress::exception::ql_exception_kind::QLExceptionKind;
use qlexpress::exception::QLException;
use qlexpress::ql_options::QLOptions;
use qlexpress::runtime::value::{DataValue, QValue};
use qlexpress::Express4Runner;
fn opts_no_cache() -> QLOptions {
QLOptions::builder().cache(false).build()
}
#[test]
fn doc_add_function_and_operator() {
let mut runner = Express4Runner::new();
runner.add_varargs_function(
"join",
|params: &[qlexpress::runtime::value::DataValue]| -> Result<_, _> {
let joined = (0..params.len())
.map(|i| params[i].string_value_of())
.collect::<Vec<_>>()
.join(",");
Ok(DataValue::string(joined))
},
);
assert_eq!(
runner
.execute("join(1,2,3)", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result(),
DataValue::Str("1,2,3".into())
);
runner.add_operator_bi("join", |left: DataValue, right: DataValue| {
DataValue::string(format!(
"{},{}",
left.string_value_of(),
right.string_value_of()
))
});
assert_eq!(
runner
.execute("1 join 2 join 3", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result(),
DataValue::Str("1,2,3".into())
);
}
#[test]
fn replace_default_operator() {
let mut runner = Express4Runner::new();
assert_eq!(
runner
.execute("'1.2'+'2.3'", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result(),
DataValue::Str("1.22.3".into())
);
let replaced = runner.replace_operator(
"+",
Rc::new(|left: &QValue, right: &QValue| {
let l: f64 = left.get().string_value_of().parse().unwrap_or(f64::NAN);
let r: f64 = right.get().string_value_of().parse().unwrap_or(f64::NAN);
Ok(DataValue::Double(l + r))
}),
);
assert!(replaced);
assert_eq!(
runner
.execute("'1.2'+'2.3'", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result(),
DataValue::Double(3.5)
);
}
#[test]
fn custom_operator_invalid_argument_keeps_user_defined_error_category() {
let mut runner = Express4Runner::new();
assert!(runner.add_operator(
"badarg",
Rc::new(|_left: &QValue, _right: &QValue| {
Err(QLException::for_test(
QLExceptionKind::Runtime,
"bad input",
error_codes::INVALID_ARGUMENT,
))
}),
));
let error = runner
.execute("1 badarg 2", HashMap::new(), &opts_no_cache())
.expect_err("custom argument failure");
assert_eq!(error.error_code(), error_codes::INVALID_ARGUMENT);
assert_eq!(error.reason(), "bad input");
}
#[test]
fn custom_operator_business_error_keeps_user_defined_error_category() {
let mut runner = Express4Runner::new();
assert!(runner.add_operator(
"badbiz",
Rc::new(|_left: &QValue, _right: &QValue| {
Err(QLException::for_test(
QLExceptionKind::Runtime,
"business rejected",
error_codes::BIZ_EXCEPTION,
))
}),
));
let error = runner
.execute("1 badbiz 2", HashMap::new(), &opts_no_cache())
.expect_err("custom business failure");
assert_eq!(error.error_code(), error_codes::BIZ_EXCEPTION);
assert_eq!(error.reason(), "business rejected");
}
#[test]
fn custom_operator_host_failure_is_wrapped_and_keeps_cause() {
let mut runner = Express4Runner::new();
assert!(runner.add_operator(
"hostfail",
Rc::new(|_left: &QValue, _right: &QValue| {
Err(QLException::host_error(
QLExceptionKind::Runtime,
"host exploded",
"HOST_FAILURE",
))
}),
));
let error = runner
.execute("1 hostfail 2", HashMap::new(), &opts_no_cache())
.expect_err("host custom operator failure");
assert_eq!(error.error_code(), "OPERATOR_INNER_EXCEPTION");
let cause = error.cause().expect("host error must remain the cause");
assert_eq!(cause.error_code(), "HOST_FAILURE");
assert_eq!(cause.reason(), "host exploded");
}
#[test]
fn custom_operator_arithmetic_failure_is_wrapped() {
let mut runner = Express4Runner::new();
assert!(runner.add_operator(
"arithfail",
Rc::new(|_left: &QValue, _right: &QValue| {
Err(QLException::for_test(
QLExceptionKind::Runtime,
"division by zero",
"ARITHMETIC_EXCEPTION",
))
}),
));
let error = runner
.execute("1 arithfail 2", HashMap::new(), &opts_no_cache())
.expect_err("arithmetic custom operator failure");
assert_eq!(error.error_code(), "OPERATOR_INNER_EXCEPTION");
assert!(error.cause().is_none());
}
#[test]
fn add_operator_with_group_precedence() {
let mut runner = Express4Runner::new();
runner.add_operator_bi("join", |left: DataValue, right: DataValue| {
DataValue::string(format!(
"{}{}",
left.string_value_of(),
right.string_value_of()
))
});
assert_eq!(
runner
.execute("1.2 join 2", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result(),
DataValue::Str("1.22".into())
);
runner.add_operator_with_precedence(
".*",
Rc::new(|left: &QValue, right: &QValue| {
let field = right.get().string_value_of();
match left.get() {
DataValue::List(list) => {
let projected: Vec<DataValue> = list
.borrow()
.iter()
.map(|item| match item {
DataValue::Map(map) => map
.borrow()
.get(&DataValue::string(field.clone()))
.cloned()
.unwrap_or(DataValue::Null),
_ => DataValue::Null,
})
.collect();
Ok(DataValue::list(projected))
}
_ => Ok(DataValue::Null),
}
}),
qlexpress::ql_precedences::GROUP,
);
match runner
.execute("[{a:1}, {a:5}].*a", HashMap::new(), &opts_no_cache())
.unwrap()
.into_result()
{
DataValue::List(list) => {
assert_eq!(*list.borrow(), vec![DataValue::Int(1), DataValue::Int(5)]);
}
other => panic!("期望 List,实际为 {other:?}"),
}
match runner
.execute(
"[{a:1}, {a:5}, {a:10}, {a:20}].*a[1:-1]",
HashMap::new(),
&QLOptions::builder().build(),
)
.unwrap()
.into_result()
{
DataValue::List(list) => {
assert_eq!(*list.borrow(), vec![DataValue::Int(5), DataValue::Int(10)]);
}
other => panic!("期望 List,实际为 {other:?}"),
}
}