2025-08-08 07:06:47 +02:00
|
|
|
use cel::{Context, Program, Value};
|
2025-08-08 07:17:39 +02:00
|
|
|
use js_sys;
|
2025-08-08 06:56:49 +02:00
|
|
|
use std::collections::HashMap;
|
2025-08-08 07:06:47 +02:00
|
|
|
use wasm_bindgen::prelude::*;
|
2025-08-07 21:10:17 +02:00
|
|
|
|
2025-08-08 07:06:47 +02:00
|
|
|
// 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()))
|
|
|
|
|
}
|
2025-08-07 21:10:17 +02:00
|
|
|
|
2025-08-08 07:06:47 +02:00
|
|
|
#[wasm_bindgen]
|
|
|
|
|
pub struct CelResult {
|
|
|
|
|
success: bool,
|
|
|
|
|
result: String,
|
|
|
|
|
error: Option<String>,
|
2025-08-07 21:10:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:06:47 +02:00
|
|
|
#[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<String> {
|
|
|
|
|
self.error.clone()
|
2025-08-07 21:10:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:06:47 +02:00
|
|
|
/// Convert a JavaScript value to a CEL Value recursively
|
|
|
|
|
fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
|
|
|
|
|
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 {
|
2025-08-08 07:17:39 +02:00
|
|
|
Err(format!("Unsupported JavaScript type"))
|
2025-08-08 06:56:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:06:47 +02:00
|
|
|
/// 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),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:17:39 +02:00
|
|
|
/// Evaluate a CEL expression and return the result as a string
|
2025-08-08 07:06:47 +02:00
|
|
|
#[wasm_bindgen]
|
2025-08-08 07:17:39 +02:00
|
|
|
pub fn evaluate_cel_string(expression: &str, context: &JsValue) -> Result<String, JsValue> {
|
2025-08-08 07:06:47 +02:00
|
|
|
match evaluate_cel_internal(expression, context) {
|
2025-08-08 07:17:39 +02:00
|
|
|
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())
|
|
|
|
|
}
|
2025-08-08 07:06:47 +02:00
|
|
|
Err(e) => Err(JsValue::from_str(&e)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn evaluate_cel_internal(expression: &str, context: &JsValue) -> Result<Value, String> {
|
|
|
|
|
// 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))?;
|
2025-08-08 06:56:49 +02:00
|
|
|
}
|
2025-08-08 07:06:47 +02:00
|
|
|
} else {
|
|
|
|
|
return Err("Context must be an object".to_string());
|
2025-08-08 06:56:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute the program
|
2025-08-08 07:06:47 +02:00
|
|
|
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!");
|
2025-08-07 21:10:17 +02:00
|
|
|
}
|