use cel::{Context, Program, Value}; use js_sys; use std::collections::HashMap; use wasm_bindgen::prelude::*; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator. #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } #[wasm_bindgen] pub struct CelResult { success: bool, result: String, error: Option, } #[wasm_bindgen] impl CelResult { #[wasm_bindgen(getter)] pub fn success(&self) -> bool { self.success } #[wasm_bindgen(getter)] pub fn result(&self) -> String { self.result.clone() } #[wasm_bindgen(getter)] pub fn error(&self) -> Option { self.error.clone() } } /// Convert a JavaScript value to a CEL Value recursively fn js_value_to_cel_value(js_val: &JsValue) -> Result { if js_val.is_null() || js_val.is_undefined() { Ok(Value::Null) } else if let Some(b) = js_val.as_bool() { Ok(Value::Bool(b)) } else if let Some(n) = js_val.as_f64() { // Check if it's an integer if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 { Ok(Value::Int(n as i64)) } else { Ok(Value::Float(n)) } } else if let Some(s) = js_val.as_string() { Ok(Value::String(s.into())) } else if js_val.is_array() { // Handle arrays let array = js_sys::Array::from(js_val); let mut cel_vec = Vec::new(); for i in 0..array.length() { let item = array.get(i); let cel_item = js_value_to_cel_value(&item)?; cel_vec.push(cel_item); } Ok(Value::List(cel_vec.into())) } else if js_val.is_object() { // Handle objects let obj = js_sys::Object::from(js_val.clone()); let entries = js_sys::Object::entries(&obj); let mut cel_map = HashMap::new(); for i in 0..entries.length() { let entry = js_sys::Array::from(&entries.get(i)); let key = entry .get(0) .as_string() .ok_or_else(|| "Object key must be a string".to_string())?; let value = entry.get(1); let cel_value = js_value_to_cel_value(&value)?; cel_map.insert(key, cel_value); } Ok(Value::Map(cel_map.into())) } else { Err(format!("Unsupported JavaScript type")) } } /// Evaluate a CEL expression with the given context #[wasm_bindgen] pub fn evaluate_cel(expression: &str, context: &JsValue) -> CelResult { let result = evaluate_cel_internal(expression, context); match result { Ok(value) => CelResult { success: true, result: format!("{:?}", value), error: None, }, Err(error) => CelResult { success: false, result: String::new(), error: Some(error), }, } } /// Evaluate a CEL expression and return the result as a string #[wasm_bindgen] pub fn evaluate_cel_string(expression: &str, context: &JsValue) -> Result { match evaluate_cel_internal(expression, context) { Ok(cel_value) => { // Convert the result to a reasonable string representation let result_string = match &cel_value { Value::String(s) => s.clone(), _ => format!("{:?}", cel_value).into(), }; Ok(result_string.to_string()) } Err(e) => Err(JsValue::from_str(&e)), } } fn evaluate_cel_internal(expression: &str, context: &JsValue) -> Result { // Compile the CEL expression let program = Program::compile(expression).map_err(|e| format!("Compilation error: {}", e))?; // Create context let mut cel_context = Context::default(); // Add context variables if provided if !context.is_undefined() && !context.is_null() { if context.is_object() { let obj = js_sys::Object::from(context.clone()); let entries = js_sys::Object::entries(&obj); for i in 0..entries.length() { let entry = js_sys::Array::from(&entries.get(i)); let key = entry .get(0) .as_string() .ok_or_else(|| "Context key must be a string".to_string())?; let value = entry.get(1); let cel_value = js_value_to_cel_value(&value) .map_err(|e| format!("Failed to convert context value '{}': {}", key, e))?; cel_context .add_variable(&key, cel_value) .map_err(|e| format!("Failed to add context variable '{}': {}", key, e))?; } } else { return Err("Context must be an object".to_string()); } } // Execute the program program .execute(&cel_context) .map_err(|e| format!("Execution error: {}", e)) } /// Initialize the WASM module (optional, for debugging) #[wasm_bindgen(start)] pub fn main() { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); console_log!("CEL-Rust WASM module initialized!"); }