Files
cel-rust-wasm/src/lib.rs

232 lines
7.5 KiB
Rust
Raw Normal View History

2025-08-08 07:06:47 +02:00
use cel::{Context, Program, Value};
use serde_wasm_bindgen;
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 js_val.is_bigint() {
// Handle BigInt
match js_val.as_f64() {
Some(n) => Ok(Value::Int(n as i64)),
None => Err("Failed to convert BigInt to number".to_string()),
}
} 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: {:?}", js_val))
}
2025-08-07 21:10:17 +02:00
}
2025-08-08 07:06:47 +02:00
/// Convert a CEL Value back to a JavaScript value
fn cel_value_to_js_value(cel_val: &Value) -> Result<JsValue, String> {
match cel_val {
Value::Null => Ok(JsValue::NULL),
Value::Bool(b) => Ok(JsValue::from_bool(*b)),
Value::Int(i) => Ok(JsValue::from_f64(*i as f64)),
Value::UInt(u) => Ok(JsValue::from_f64(*u as f64)),
Value::Float(f) => Ok(JsValue::from_f64(*f)),
Value::String(s) => Ok(JsValue::from_str(s)),
Value::Bytes(b) => {
// Convert bytes to Uint8Array
let array = js_sys::Uint8Array::new_with_length(b.len() as u32);
for (i, byte) in b.iter().enumerate() {
array.set_index(i as u32, *byte);
}
2025-08-08 07:06:47 +02:00
Ok(array.into())
}
2025-08-08 07:06:47 +02:00
Value::List(list) => {
let array = js_sys::Array::new();
for item in list.iter() {
let js_item = cel_value_to_js_value(item)?;
array.push(&js_item);
}
Ok(array.into())
}
2025-08-08 07:06:47 +02:00
Value::Map(map) => {
let obj = js_sys::Object::new();
for (key, value) in map.iter() {
let js_value = cel_value_to_js_value(value)?;
js_sys::Reflect::set(&obj, &JsValue::from_str(key), &js_value)
.map_err(|_| "Failed to set object property".to_string())?;
}
2025-08-08 07:06:47 +02:00
Ok(obj.into())
}
Value::Duration(d) => {
// Convert duration to string representation
Ok(JsValue::from_str(&format!("{}s", d.as_secs_f64())))
}
Value::Timestamp(ts) => {
// Convert timestamp to ISO string
let datetime = chrono::DateTime::from_timestamp(ts.as_secs() as i64, ts.subsec_nanos())
.ok_or_else(|| "Invalid timestamp".to_string())?;
Ok(JsValue::from_str(&datetime.to_rfc3339()))
}
2025-08-08 07:06:47 +02:00
_ => Err(format!(
"Unsupported CEL type for conversion: {:?}",
cel_val
)),
}
}
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),
},
}
}
/// Evaluate a CEL expression and return the result as a JavaScript value
#[wasm_bindgen]
pub fn evaluate_cel_js(expression: &str, context: &JsValue) -> Result<JsValue, JsValue> {
match evaluate_cel_internal(expression, context) {
Ok(cel_value) => match cel_value_to_js_value(&cel_value) {
Ok(js_val) => Ok(js_val),
Err(e) => Err(JsValue::from_str(&format!("Conversion error: {}", e))),
},
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 07:06:47 +02:00
} else {
return Err("Context must be an object".to_string());
}
}
// 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
}