use std::collections::HashMap;
use std::sync::OnceLock;
use cel_interpreter::{Context, Program, Value};
use serde_json::Value as JsonValue;
use super::error::{ExpressionError, ExpressionResult};
use super::profile::{self, ProfileConfig};
static PROFILE_CONFIG: OnceLock<ProfileConfig> = OnceLock::new();
fn get_profile_config() -> &'static ProfileConfig {
PROFILE_CONFIG.get_or_init(|| {
#[cfg(feature = "config")]
{
if let Some(cfg) = crate::config::try_get()
&& let Ok(profile) = cfg.unmarshal_key_registered::<ProfileConfig>("expression")
{
return profile;
}
}
ProfileConfig::default()
})
}
#[must_use]
pub fn validate(expr: &str) -> Vec<String> {
validate_with_config(expr, get_profile_config())
}
#[must_use]
pub fn validate_with_config(expr: &str, config: &ProfileConfig) -> Vec<String> {
if expr.trim().is_empty() {
return vec!["Expression is empty".to_string()];
}
let profile_errors = profile::check_profile_with_config(expr, config);
if !profile_errors.is_empty() {
return profile_errors;
}
match Program::compile(expr) {
Ok(_) => vec![],
Err(e) => vec![format!("{e}")],
}
}
pub fn compile(expr: &str) -> ExpressionResult<Program> {
compile_with_config(expr, get_profile_config())
}
pub fn compile_with_config(expr: &str, config: &ProfileConfig) -> ExpressionResult<Program> {
let errors = validate_with_config(expr, config);
if !errors.is_empty() {
return Err(ExpressionError::Validation(errors));
}
Program::compile(expr).map_err(|e| ExpressionError::Compilation(format!("{e}")))
}
pub fn evaluate<'a>(
expr: &str,
data: impl IntoIterator<Item = (&'a String, &'a JsonValue)>,
) -> ExpressionResult<Value> {
let program = compile(expr)?;
let context = build_context(data)?;
program
.execute(&context)
.map_err(|e| ExpressionError::Evaluation(format!("{e}")))
}
pub fn build_context<'a>(
data: impl IntoIterator<Item = (&'a String, &'a JsonValue)>,
) -> ExpressionResult<Context<'a>> {
let mut context = Context::default();
for (key, value) in data {
context.add_variable_from_value(key, json_to_cel(value));
}
Ok(context)
}
#[must_use]
pub fn evaluate_condition<'a>(
expr: &str,
data: impl IntoIterator<Item = (&'a String, &'a JsonValue)>,
) -> bool {
match evaluate(expr, data) {
Ok(Value::Bool(b)) => b,
Ok(Value::Int(n)) => n != 0,
Ok(Value::UInt(n)) => n != 0,
Ok(Value::Float(f)) => f != 0.0,
_ => false,
}
}
fn json_to_cel(json: &JsonValue) -> Value {
match json {
JsonValue::Null => Value::Null,
JsonValue::Bool(b) => Value::Bool(*b),
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else if let Some(u) = n.as_u64() {
Value::UInt(u)
} else {
Value::Float(n.as_f64().unwrap_or(0.0))
}
}
JsonValue::String(s) => Value::String(s.clone().into()),
JsonValue::Array(arr) => {
Value::List(arr.iter().map(json_to_cel).collect::<Vec<_>>().into())
}
JsonValue::Object(obj) => {
let hash: HashMap<cel_interpreter::objects::Key, Value> = obj
.iter()
.map(|(k, v)| (k.clone().into(), json_to_cel(v)))
.collect();
Value::Map(hash.into())
}
}
}