switch to wasm-bindgen
This commit is contained in:
304
src/lib.rs
304
src/lib.rs
@@ -1,109 +1,231 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
|
||||
use cel::{Context, Program, Value};
|
||||
use serde_json;
|
||||
use serde_wasm_bindgen;
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn allocation(n: usize) -> *mut u8 {
|
||||
mem::ManuallyDrop::new(Vec::with_capacity(n)).as_mut_ptr()
|
||||
// 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);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn evaluate(s: *const u8) -> *mut u8 {
|
||||
unsafe {
|
||||
let length = u32::from_le_bytes(*(s as *const [u8; 4])) as usize;
|
||||
let input = slice::from_raw_parts(s.offset(4), length);
|
||||
let output = evaluate_buffers(input);
|
||||
mem::ManuallyDrop::new(output).as_mut_ptr()
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct CelResult {
|
||||
success: bool,
|
||||
result: String,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_buffers(input: &[u8]) -> Vec<u8> {
|
||||
let contents = str::from_utf8(input).unwrap();
|
||||
let result = evaluate_cel_with_context(contents);
|
||||
let success = result.is_ok();
|
||||
let message = result.unwrap_or_else(|e| e);
|
||||
let len = message.len();
|
||||
let mut buffer = Vec::with_capacity(len + 8);
|
||||
buffer.push(if success { 1 } else { 0 });
|
||||
buffer.extend(vec![0; 3]);
|
||||
buffer.extend_from_slice(&(len as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(message.as_bytes());
|
||||
buffer
|
||||
}
|
||||
/// 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();
|
||||
|
||||
fn json_to_cel_value(json_value: &serde_json::Value) -> Value {
|
||||
match json_value {
|
||||
serde_json::Value::Null => Value::Null,
|
||||
serde_json::Value::Bool(b) => Value::Bool(*b),
|
||||
serde_json::Value::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
Value::Int(i)
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Value::Float(f)
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
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);
|
||||
}
|
||||
serde_json::Value::String(s) => Value::String(s.clone().into()),
|
||||
serde_json::Value::Array(arr) => {
|
||||
let cel_vec: Vec<Value> = arr.iter().map(json_to_cel_value).collect();
|
||||
Value::List(cel_vec.into())
|
||||
}
|
||||
serde_json::Value::Object(obj) => {
|
||||
let mut cel_map = HashMap::new();
|
||||
for (key, value) in obj.iter() {
|
||||
cel_map.insert(key.clone(), json_to_cel_value(value));
|
||||
}
|
||||
Value::Map(cel_map.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_cel_with_context(content: &str) -> Result<String, String> {
|
||||
// Split the input to get expression and context
|
||||
let parts: Vec<&str> = content.split("\n---CONTEXT---\n").collect();
|
||||
let expression = parts[0].trim();
|
||||
let context_json = if parts.len() > 1 {
|
||||
parts[1].trim()
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the CEL expression
|
||||
let program = match Program::compile(expression) {
|
||||
Ok(prog) => prog,
|
||||
Err(e) => return Err(format!("Compilation error: {}", e)),
|
||||
};
|
||||
|
||||
// Create context and add JSON values
|
||||
let mut context = Context::default();
|
||||
|
||||
// Parse JSON context if provided and not empty
|
||||
if !context_json.is_empty() && context_json != "{}" {
|
||||
match serde_json::from_str::<serde_json::Value>(context_json) {
|
||||
Ok(json_value) => {
|
||||
if let serde_json::Value::Object(obj) = json_value {
|
||||
for (key, value) in obj.iter() {
|
||||
let cel_value = json_to_cel_value(value);
|
||||
context
|
||||
.add_variable(key, cel_value)
|
||||
.map_err(|e| format!("Context error: {}", e))?;
|
||||
}
|
||||
} else {
|
||||
return Err("JSON context must be an object".to_string());
|
||||
}
|
||||
/// 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);
|
||||
}
|
||||
Err(e) => return Err(format!("JSON parsing error: {}", e)),
|
||||
Ok(array.into())
|
||||
}
|
||||
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())
|
||||
}
|
||||
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())?;
|
||||
}
|
||||
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()))
|
||||
}
|
||||
_ => Err(format!(
|
||||
"Unsupported CEL type for conversion: {:?}",
|
||||
cel_val
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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))?;
|
||||
}
|
||||
} else {
|
||||
return Err("Context must be an object".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the program
|
||||
match program.execute(&context) {
|
||||
Ok(value) => Ok(format!("{:?}", value)),
|
||||
Err(e) => Err(format!("Execution error: {}", e)),
|
||||
}
|
||||
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!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user