Compare commits

2 Commits
main ... v0.2.0

Author SHA1 Message Date
4973376639 cleanup 2025-08-08 10:11:54 +02:00
1cce02a8eb convert back to JS values 2025-08-08 10:11:54 +02:00
5 changed files with 65 additions and 112 deletions

3
Cargo.lock generated
View File

@@ -115,12 +115,13 @@ dependencies = [
[[package]]
name = "cel-rust-wasm"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"cel",
"chrono",
"console_error_panic_hook",
"js-sys",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"web-sys",

View File

@@ -10,6 +10,7 @@ crate-type = ["cdylib"]
wasm-bindgen = "0.2"
js-sys = "0.3"
cel = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -19,13 +19,8 @@ To build and use this:
4. Use the API:
```js
// Method 1: Get structured result
const result = evaluate_cel("name + ' is ' + string(age)", {name: "Alice", age: 30});
console.log(result.success, result.result, result.error);
// Method 2: Get string result or error
try {
const result = evaluate_cel_string("name + ' is ' + string(age)", {name: "Alice", age: 30});
const result = evaluate_cel("name + ' is ' + string(age)", {name: "Alice", age: 30});
console.log(result);
} catch (error) {
console.error(error);

View File

@@ -113,8 +113,8 @@
<body>
<h1>CEL-Rust WASM-bindgen Demo</h1>
<p>
This demo uses <code>wasm-bindgen</code> to provide a clean JavaScript API
for CEL evaluation.
This demo uses <code>wasm-bindgen</code> to provide a JavaScript API for
CEL evaluation using <code>cel-rust</code>.
</p>
<div class="container">
@@ -136,14 +136,9 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
</div>
</div>
<div class="full-width">
<button onclick="evaluate()">Evaluate CEL Expression</button>
<button onclick="evaluateAsString()">Evaluate & Return as String</button>
</div>
<div class="full-width">
<label>Result:</label>
<div id="result" class="result">Click "Evaluate" to see results...</div>
<div id="result" class="result">WASM module not loaded yet...</div>
</div>
<div class="examples">
@@ -162,9 +157,9 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
<div class="example" onclick="loadExample(this)">
<h4>Math Operations</h4>
<code
data-expression="(price * quantity) * (1 + tax)"
data-expression="(price * double(quantity)) * (1.0 + tax)"
data-context='{"price": 10.50, "quantity": 3, "tax": 0.08}'
>Expression: (price * quantity) * (1 + tax)</code
>Expression: (price * double(quantity)) * (1.0 + tax)</code
>
<code>Context: {"price": 10.50, "quantity": 3, "tax": 0.08}</code>
</div>
@@ -172,9 +167,10 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
<div class="example" onclick="loadExample(this)">
<h4>List Operations</h4>
<code
data-expression="scores.filter(s, s > 90).size() + ' high scores out of ' + string(scores.size())"
data-expression="string(scores.filter(s, s > 90).size()) + ' high scores out of ' + string(scores.size())"
data-context='{"scores": [85, 92, 78, 96, 88]}'
>Expression: scores.filter(s, s > 90).size() + ' high scores'</code
>Expression: string(scores.filter(s, s > 90).size()) + ' high
scores'</code
>
<code>Context: {"scores": [85, 92, 78, 96, 88]}</code>
</div>
@@ -206,10 +202,7 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
</div>
<script type="module">
import init, {
evaluate_cel,
evaluate_cel_string,
} from "./pkg/cel_rust_wasm.js";
import init, { evaluate_cel } from "./pkg/cel_rust_wasm.js";
let wasmModule;
@@ -217,12 +210,10 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
wasmModule = await init();
console.log("WASM module loaded successfully");
// Make functions globally available
// Make function globally available
window.evaluate_cel = evaluate_cel;
window.evaluate_cel_string = evaluate_cel_string;
// Enable the evaluate button
document.querySelector("button").disabled = false;
setTimeout(evaluate, 0);
}
// Initialize WASM when the page loads
@@ -230,7 +221,6 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
// Make functions globally available for onclick handlers
window.evaluate = evaluate;
window.evaluateAsString = evaluateAsString;
window.loadExample = loadExample;
</script>
@@ -250,45 +240,19 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
// Parse the context as JavaScript object
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);
if (result.success) {
resultDiv.className = "result success";
resultDiv.textContent = `Success: ${result.result}`;
} else {
resultDiv.className = "result error";
resultDiv.textContent = `Error: ${result.error}`;
}
} catch (e) {
resultDiv.className = "result error";
resultDiv.textContent = `JavaScript Error: ${e.message}`;
}
}
function evaluateAsString() {
if (!window.evaluate_cel_string) {
document.getElementById("result").textContent =
"WASM module not loaded yet...";
return;
}
const expression = document.getElementById("expression").value;
const contextStr = document.getElementById("context").value;
const resultDiv = document.getElementById("result");
try {
// Parse the context as JavaScript object
const context = contextStr.trim() ? JSON.parse(contextStr) : {};
// Evaluate using the string result version
const result = window.evaluate_cel_string(expression, context);
resultDiv.className = "result success";
resultDiv.textContent = `Success (String): ${result}`;
resultDiv.textContent = `Success (type '${typeof result}'): ${JSON.stringify(result, null, 2)}`;
// Log the actual JavaScript object to console for inspection
console.log("CEL Result:", result);
console.log("Type:", typeof result);
} catch (e) {
resultDiv.className = "result error";
resultDiv.textContent = `Error: ${e}`;
console.log("CEL Error:", e);
}
}
@@ -301,16 +265,16 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
document.getElementById("context").value = context;
// Auto-evaluate
setTimeout(evaluate, 100);
setTimeout(evaluate, 0);
}
// Auto-evaluate on input change
document.getElementById("expression").addEventListener("input", () => {
setTimeout(evaluate, 300);
setTimeout(evaluate, 100);
});
document.getElementById("context").addEventListener("input", () => {
setTimeout(evaluate, 300);
setTimeout(evaluate, 100);
});
</script>
</body>

View File

@@ -1,5 +1,6 @@
use cel::{Context, Program, Value};
use js_sys;
use serde::Serialize;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
@@ -19,31 +20,6 @@ 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()
}
}
/// 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() {
@@ -94,36 +70,52 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
}
}
/// 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);
// Simple wrapper to make Value serializable for JS conversion
#[derive(Serialize)]
#[serde(untagged)]
enum SerializableValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
List(Vec<SerializableValue>),
Map(HashMap<String, SerializableValue>),
Other(String),
}
match result {
Ok(value) => CelResult {
success: true,
result: format!("{:?}", value),
error: None,
},
Err(error) => CelResult {
success: false,
result: String::new(),
error: Some(error),
},
fn cel_value_to_serializable(cel_val: &Value) -> SerializableValue {
match cel_val {
Value::Null => SerializableValue::Null,
Value::Bool(b) => SerializableValue::Bool(*b),
Value::Int(i) => SerializableValue::Int(*i),
Value::UInt(u) => SerializableValue::Int(*u as i64),
Value::Float(f) => SerializableValue::Float(*f),
Value::String(s) => SerializableValue::String(s.to_string()),
Value::List(_list) => {
SerializableValue::List(_list.iter().map(cel_value_to_serializable).collect())
}
Value::Map(_map) => SerializableValue::Map(
_map.map
.iter()
.map(|(k, v)| (k.to_string(), cel_value_to_serializable(v)))
.collect(),
),
_ => 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]
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) {
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())
// Convert to serializable format and then to JS
let serializable = cel_value_to_serializable(&cel_value);
match serde_wasm_bindgen::to_value(&serializable) {
Ok(js_val) => Ok(js_val),
Err(e) => Err(JsValue::from_str(&format!("Serialization error: {}", e))),
}
}
Err(e) => Err(JsValue::from_str(&e)),
}