convert back to JS values

This commit is contained in:
2025-08-08 07:38:48 +02:00
parent 8453d50220
commit 537d64a1b0
4 changed files with 78 additions and 72 deletions

1
Cargo.lock generated
View File

@@ -121,6 +121,7 @@ dependencies = [
"chrono", "chrono",
"console_error_panic_hook", "console_error_panic_hook",
"js-sys", "js-sys",
"serde",
"serde-wasm-bindgen", "serde-wasm-bindgen",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cel-rust-wasm" name = "cel-rust-wasm"
version = "0.2.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lib] [lib]
@@ -10,6 +10,7 @@ crate-type = ["cdylib"]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
js-sys = "0.3" js-sys = "0.3"
cel = "0.11" cel = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6" serde-wasm-bindgen = "0.6"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }

View File

@@ -137,8 +137,8 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
</div> </div>
<div class="full-width"> <div class="full-width">
<button onclick="evaluate()">Evaluate CEL Expression</button> <button onclick="evaluateAsJs()">Evaluate as JavaScript Value</button>
<button onclick="evaluateAsString()">Evaluate & Return as String</button> <button onclick="evaluateAsDebug()">Evaluate & Show Debug</button>
</div> </div>
<div class="full-width"> <div class="full-width">
@@ -208,7 +208,7 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
<script type="module"> <script type="module">
import init, { import init, {
evaluate_cel, evaluate_cel,
evaluate_cel_string, evaluate_cel_debug,
} from "./pkg/cel_rust_wasm.js"; } from "./pkg/cel_rust_wasm.js";
let wasmModule; let wasmModule;
@@ -219,7 +219,7 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
// Make functions globally available // Make functions globally available
window.evaluate_cel = evaluate_cel; window.evaluate_cel = evaluate_cel;
window.evaluate_cel_string = evaluate_cel_string; window.evaluate_cel_debug = evaluate_cel_debug;
// Enable the evaluate button // Enable the evaluate button
document.querySelector("button").disabled = false; document.querySelector("button").disabled = false;
@@ -229,13 +229,13 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
initWasm().catch(console.error); initWasm().catch(console.error);
// Make functions globally available for onclick handlers // Make functions globally available for onclick handlers
window.evaluate = evaluate; window.evaluateAsJs = evaluateAsJs;
window.evaluateAsString = evaluateAsString; window.evaluateAsDebug = evaluateAsDebug;
window.loadExample = loadExample; window.loadExample = loadExample;
</script> </script>
<script> <script>
function evaluate() { function evaluateAsJs() {
if (!window.evaluate_cel) { if (!window.evaluate_cel) {
document.getElementById("result").textContent = document.getElementById("result").textContent =
"WASM module not loaded yet..."; "WASM module not loaded yet...";
@@ -250,24 +250,23 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
// Parse the context as JavaScript object // Parse the context as JavaScript object
const context = contextStr.trim() ? JSON.parse(contextStr) : {}; const context = contextStr.trim() ? JSON.parse(contextStr) : {};
// Evaluate using the string result version // Evaluate and get the actual JavaScript value
const result = window.evaluate_cel(expression, context); const result = window.evaluate_cel(expression, context);
if (result.success) {
resultDiv.className = "result success"; resultDiv.className = "result success";
resultDiv.textContent = `Success: ${result.result}`; resultDiv.textContent = `Success (JS Value): ${JSON.stringify(result, null, 2)}`;
} else {
resultDiv.className = "result error"; // Log the actual JavaScript object to console for inspection
resultDiv.textContent = `Error: ${result.error}`; console.log("CEL Result as JavaScript value:", result);
} console.log("Type:", typeof result);
} catch (e) { } catch (e) {
resultDiv.className = "result error"; resultDiv.className = "result error";
resultDiv.textContent = `JavaScript Error: ${e.message}`; resultDiv.textContent = `Error: ${e}`;
} }
} }
function evaluateAsString() { function evaluateAsDebug() {
if (!window.evaluate_cel_string) { if (!window.evaluate_cel_debug) {
document.getElementById("result").textContent = document.getElementById("result").textContent =
"WASM module not loaded yet..."; "WASM module not loaded yet...";
return; return;
@@ -281,11 +280,11 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
// Parse the context as JavaScript object // Parse the context as JavaScript object
const context = contextStr.trim() ? JSON.parse(contextStr) : {}; const context = contextStr.trim() ? JSON.parse(contextStr) : {};
// Evaluate using the string result version // Evaluate and get debug string
const result = window.evaluate_cel_string(expression, context); const result = window.evaluate_cel_debug(expression, context);
resultDiv.className = "result success"; resultDiv.className = "result success";
resultDiv.textContent = `Success (String): ${result}`; resultDiv.textContent = `Debug Output: ${result}`;
} catch (e) { } catch (e) {
resultDiv.className = "result error"; resultDiv.className = "result error";
resultDiv.textContent = `Error: ${e}`; resultDiv.textContent = `Error: ${e}`;
@@ -301,15 +300,18 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
document.getElementById("context").value = context; document.getElementById("context").value = context;
// Auto-evaluate // Auto-evaluate
setTimeout(evaluate, 100); setTimeout(evaluateAsJs, 100);
} }
// Auto-evaluate on input change // Auto-evaluate on input change
document.getElementById("expression").addEventListener("input", () => { document.getElementById("expression").addEventListener("input", () => {
setTimeout(evaluate, 300); setTimeout(evaluateAsJs, 300);
}); });
document.getElementById("context").addEventListener("input", () => { document.getElementById("context").addEventListener("input", () => {
setTimeout(evaluateAsJs, 300);
});
addEventListener("input", () => {
setTimeout(evaluate, 300); setTimeout(evaluate, 300);
}); });
</script> </script>

View File

@@ -1,5 +1,6 @@
use cel::{Context, Program, Value}; use cel::{Context, Program, Value};
use js_sys; use js_sys;
use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@@ -19,31 +20,6 @@ macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string())) ($($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()
}
}
/// Convert a JavaScript value to a CEL Value recursively /// Convert a JavaScript value to a CEL Value recursively
fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> { fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
if js_val.is_null() || js_val.is_undefined() { if js_val.is_null() || js_val.is_undefined() {
@@ -94,37 +70,63 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
} }
} }
/// Evaluate a CEL expression with the given context // Simple wrapper to make Value serializable for JS conversion
#[wasm_bindgen] #[derive(Serialize)]
pub fn evaluate_cel(expression: &str, context: &JsValue) -> CelResult { #[serde(untagged)]
let result = evaluate_cel_internal(expression, context); enum SerializableValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
List(Vec<SerializableValue>),
Map(HashMap<String, SerializableValue>),
Other(String),
}
match result { fn cel_value_to_serializable(cel_val: &Value) -> SerializableValue {
Ok(value) => CelResult { match cel_val {
success: true, Value::Null => SerializableValue::Null,
result: format!("{:?}", value), Value::Bool(b) => SerializableValue::Bool(*b),
error: None, Value::Int(i) => SerializableValue::Int(*i),
}, Value::UInt(u) => SerializableValue::Int(*u as i64),
Err(error) => CelResult { Value::Float(f) => SerializableValue::Float(*f),
success: false, Value::String(s) => SerializableValue::String(s.to_string()),
result: String::new(), Value::List(_list) => {
error: Some(error), // For now, convert to string representation
}, // We can improve this later when we figure out the exact CEL API
SerializableValue::Other(format!("{:?}", cel_val))
}
Value::Map(_map) => {
// For now, convert to string representation
// We can improve this later when we figure out the exact CEL API
SerializableValue::Other(format!("{:?}", cel_val))
}
_ => SerializableValue::Other(format!("{:?}", cel_val)),
} }
} }
/// Evaluate a CEL expression and return the result as a string /// Evaluate a CEL expression and return the actual JavaScript value
#[wasm_bindgen] #[wasm_bindgen]
pub fn evaluate_cel_string(expression: &str, context: &JsValue) -> Result<String, JsValue> { pub fn evaluate_cel(expression: &str, context: &JsValue) -> Result<JsValue, JsValue> {
match evaluate_cel_internal(expression, context) { match evaluate_cel_internal(expression, context) {
Ok(cel_value) => { Ok(cel_value) => {
// Convert the result to a reasonable string representation // Convert to serializable format and then to JS
let result_string = match &cel_value { let serializable = cel_value_to_serializable(&cel_value);
Value::String(s) => s.clone(), match serde_wasm_bindgen::to_value(&serializable) {
_ => format!("{:?}", cel_value).into(), Ok(js_val) => Ok(js_val),
}; Err(e) => Err(JsValue::from_str(&format!("Serialization error: {}", e))),
Ok(result_string.to_string())
} }
}
Err(e) => Err(JsValue::from_str(&e)),
}
}
/// Evaluate a CEL expression and return the result as a debug string
#[wasm_bindgen]
pub fn evaluate_cel_debug(expression: &str, context: &JsValue) -> Result<String, JsValue> {
match evaluate_cel_internal(expression, context) {
Ok(cel_value) => Ok(format!("{:?}", cel_value)),
Err(e) => Err(JsValue::from_str(&e)), Err(e) => Err(JsValue::from_str(&e)),
} }
} }