Compare commits
4 Commits
main
...
8454550786
| Author | SHA1 | Date | |
|---|---|---|---|
|
8454550786
|
|||
|
695ad2e41e
|
|||
|
4973376639
|
|||
|
1cce02a8eb
|
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -115,12 +115,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cel-rust-wasm"
|
name = "cel-rust-wasm"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cel",
|
"cel",
|
||||||
"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",
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,8 @@ To build and use this:
|
|||||||
|
|
||||||
4. Use the API:
|
4. Use the API:
|
||||||
```js
|
```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 {
|
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);
|
console.log(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
90
index.html
90
index.html
@@ -113,8 +113,8 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>CEL-Rust WASM-bindgen Demo</h1>
|
<h1>CEL-Rust WASM-bindgen Demo</h1>
|
||||||
<p>
|
<p>
|
||||||
This demo uses <code>wasm-bindgen</code> to provide a clean JavaScript API
|
This demo uses <code>wasm-bindgen</code> to provide a JavaScript API for
|
||||||
for CEL evaluation.
|
CEL evaluation using <code>cel-rust</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -136,14 +136,9 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="full-width">
|
||||||
<label>Result:</label>
|
<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>
|
||||||
|
|
||||||
<div class="examples">
|
<div class="examples">
|
||||||
@@ -162,9 +157,9 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
<div class="example" onclick="loadExample(this)">
|
<div class="example" onclick="loadExample(this)">
|
||||||
<h4>Math Operations</h4>
|
<h4>Math Operations</h4>
|
||||||
<code
|
<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}'
|
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>
|
<code>Context: {"price": 10.50, "quantity": 3, "tax": 0.08}</code>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,9 +167,10 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
<div class="example" onclick="loadExample(this)">
|
<div class="example" onclick="loadExample(this)">
|
||||||
<h4>List Operations</h4>
|
<h4>List Operations</h4>
|
||||||
<code
|
<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]}'
|
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>
|
<code>Context: {"scores": [85, 92, 78, 96, 88]}</code>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,13 +199,22 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
true}}}</code
|
true}}}</code
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="example" onclick="loadExample(this)">
|
||||||
|
<h4>Time and Date Operations</h4>
|
||||||
|
<code
|
||||||
|
data-expression="some_date < now - old ? 'old' : 'recent'"
|
||||||
|
data-context='{"some_date": "2023-10-01T12:00:00Z", "now": "2023-10-03T12:00:00Z", "old": "24h"}'
|
||||||
|
>Expression: some_date < now - old ? 'old' : 'recent'</code
|
||||||
|
>
|
||||||
|
<code
|
||||||
|
>Context: {"some_date": "2023-10-01T12:00:00Z", "now":
|
||||||
|
"2023-10-03T12:00:00Z", "old": "24h"}</code
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init, {
|
import init, { evaluate_cel } from "./pkg/cel_rust_wasm.js";
|
||||||
evaluate_cel,
|
|
||||||
evaluate_cel_string,
|
|
||||||
} from "./pkg/cel_rust_wasm.js";
|
|
||||||
|
|
||||||
let wasmModule;
|
let wasmModule;
|
||||||
|
|
||||||
@@ -217,12 +222,10 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
wasmModule = await init();
|
wasmModule = await init();
|
||||||
console.log("WASM module loaded successfully");
|
console.log("WASM module loaded successfully");
|
||||||
|
|
||||||
// Make functions globally available
|
// Make function globally available
|
||||||
window.evaluate_cel = evaluate_cel;
|
window.evaluate_cel = evaluate_cel;
|
||||||
window.evaluate_cel_string = evaluate_cel_string;
|
|
||||||
|
|
||||||
// Enable the evaluate button
|
setTimeout(evaluate, 0);
|
||||||
document.querySelector("button").disabled = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize WASM when the page loads
|
// Initialize WASM when the page loads
|
||||||
@@ -230,7 +233,6 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
|||||||
|
|
||||||
// Make functions globally available for onclick handlers
|
// Make functions globally available for onclick handlers
|
||||||
window.evaluate = evaluate;
|
window.evaluate = evaluate;
|
||||||
window.evaluateAsString = evaluateAsString;
|
|
||||||
window.loadExample = loadExample;
|
window.loadExample = loadExample;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -250,45 +252,19 @@ 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.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.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) {
|
} catch (e) {
|
||||||
resultDiv.className = "result error";
|
resultDiv.className = "result error";
|
||||||
resultDiv.textContent = `Error: ${e}`;
|
resultDiv.textContent = `Error: ${e}`;
|
||||||
|
console.log("CEL Error:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,16 +277,16 @@ 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(evaluate, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-evaluate on input change
|
// Auto-evaluate on input change
|
||||||
document.getElementById("expression").addEventListener("input", () => {
|
document.getElementById("expression").addEventListener("input", () => {
|
||||||
setTimeout(evaluate, 300);
|
setTimeout(evaluate, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("context").addEventListener("input", () => {
|
document.getElementById("context").addEventListener("input", () => {
|
||||||
setTimeout(evaluate, 300);
|
setTimeout(evaluate, 100);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
205
src/lib.rs
205
src/lib.rs
@@ -1,5 +1,7 @@
|
|||||||
use cel::{Context, Program, Value};
|
use cel::{Context, Program, Value};
|
||||||
|
use chrono::Duration as ChronoDuration;
|
||||||
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,29 +21,57 @@ macro_rules! console_log {
|
|||||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
/// Parse an ISO 8601 timestamp string into a CEL Timestamp
|
||||||
pub struct CelResult {
|
fn parse_timestamp(timestamp_str: &str) -> Result<Value, String> {
|
||||||
success: bool,
|
Ok(Value::Timestamp(
|
||||||
result: String,
|
timestamp_str
|
||||||
error: Option<String>,
|
.parse()
|
||||||
|
.map_err(|e| format!("Invalid timestamp format: {}", e))?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
/// Parse a duration string (e.g., "1h30m", "45s", "2.5h") into a CEL Duration
|
||||||
impl CelResult {
|
fn parse_duration(duration_str: &str) -> Result<Value, String> {
|
||||||
#[wasm_bindgen(getter)]
|
// Parse duration strings like "1h30m45s", "2.5h", "300s", etc.
|
||||||
pub fn success(&self) -> bool {
|
let duration_str = duration_str.trim();
|
||||||
self.success
|
|
||||||
|
// Simple parser for common duration formats
|
||||||
|
let mut total_seconds = 0.0;
|
||||||
|
let mut current_number = String::new();
|
||||||
|
|
||||||
|
for ch in duration_str.chars() {
|
||||||
|
if ch.is_numeric() || ch == '.' {
|
||||||
|
current_number.push(ch);
|
||||||
|
} else {
|
||||||
|
if !current_number.is_empty() {
|
||||||
|
let value: f64 = current_number
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| format!("Invalid number in duration: {}", current_number))?;
|
||||||
|
|
||||||
|
let multiplier = match ch {
|
||||||
|
's' => 1.0,
|
||||||
|
'm' => 60.0,
|
||||||
|
'h' => 3600.0,
|
||||||
|
'd' => 86400.0,
|
||||||
|
_ => return Err(format!("Unknown duration unit: {}", ch)),
|
||||||
|
};
|
||||||
|
|
||||||
|
total_seconds += value * multiplier;
|
||||||
|
current_number.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
// Handle case where string ends with a number (assume seconds)
|
||||||
pub fn result(&self) -> String {
|
if !current_number.is_empty() {
|
||||||
self.result.clone()
|
let value: f64 = current_number
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| format!("Invalid number in duration: {}", current_number))?;
|
||||||
|
total_seconds += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
let duration = ChronoDuration::seconds(total_seconds as i64);
|
||||||
pub fn error(&self) -> Option<String> {
|
Ok(Value::Duration(duration))
|
||||||
self.error.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a JavaScript value to a CEL Value recursively
|
/// Convert a JavaScript value to a CEL Value recursively
|
||||||
@@ -58,7 +88,26 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
|
|||||||
Ok(Value::Float(n))
|
Ok(Value::Float(n))
|
||||||
}
|
}
|
||||||
} else if let Some(s) = js_val.as_string() {
|
} else if let Some(s) = js_val.as_string() {
|
||||||
Ok(Value::String(s.into()))
|
// Check if this string represents a timestamp or duration
|
||||||
|
if s.contains('T') && (s.contains('Z') || s.contains('+') || s.contains('-')) {
|
||||||
|
// Looks like an ISO 8601 timestamp
|
||||||
|
match parse_timestamp(&s) {
|
||||||
|
Ok(ts) => Ok(ts),
|
||||||
|
Err(_) => Ok(Value::String(s.into())), // Fall back to string if parsing fails
|
||||||
|
}
|
||||||
|
} else if s
|
||||||
|
.chars()
|
||||||
|
.any(|c| c == 'h' || c == 'm' || c == 's' || c == 'd')
|
||||||
|
&& s.chars().any(|c| c.is_numeric())
|
||||||
|
{
|
||||||
|
// Looks like a duration string
|
||||||
|
match parse_duration(&s) {
|
||||||
|
Ok(dur) => Ok(dur),
|
||||||
|
Err(_) => Ok(Value::String(s.into())), // Fall back to string if parsing fails
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Value::String(s.into()))
|
||||||
|
}
|
||||||
} else if js_val.is_array() {
|
} else if js_val.is_array() {
|
||||||
// Handle arrays
|
// Handle arrays
|
||||||
let array = js_sys::Array::from(js_val);
|
let array = js_sys::Array::from(js_val);
|
||||||
@@ -94,36 +143,84 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a CEL expression with the given context
|
#[derive(Serialize)]
|
||||||
#[wasm_bindgen]
|
#[serde(untagged)]
|
||||||
pub fn evaluate_cel(expression: &str, context: &JsValue) -> CelResult {
|
enum SerializableValue {
|
||||||
let result = evaluate_cel_internal(expression, context);
|
Null,
|
||||||
|
Bool(bool),
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
String(String),
|
||||||
|
List(Vec<SerializableValue>),
|
||||||
|
Map(HashMap<String, SerializableValue>),
|
||||||
|
Timestamp(String), // Serialize timestamps as ISO strings
|
||||||
|
Duration(String), // Serialize durations as strings like "1h30m45s"
|
||||||
|
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),
|
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(),
|
||||||
|
),
|
||||||
|
Value::Timestamp(ts) => SerializableValue::Timestamp(ts.to_string()),
|
||||||
|
Value::Duration(dur) => {
|
||||||
|
// Convert duration back to a readable string format
|
||||||
|
let total_seconds = dur.as_seconds_f64();
|
||||||
|
let hours = (total_seconds / 3600.0).floor() as u64;
|
||||||
|
let minutes = ((total_seconds % 3600.0) / 60.0).floor() as u64;
|
||||||
|
let seconds = total_seconds % 60.0;
|
||||||
|
|
||||||
|
if hours > 0 {
|
||||||
|
if seconds.fract() == 0.0 {
|
||||||
|
SerializableValue::Duration(format!(
|
||||||
|
"{}h{}m{}s",
|
||||||
|
hours, minutes, seconds as u64
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
SerializableValue::Duration(format!("{}h{}m{:.3}s", hours, minutes, seconds))
|
||||||
|
}
|
||||||
|
} else if minutes > 0 {
|
||||||
|
if seconds.fract() == 0.0 {
|
||||||
|
SerializableValue::Duration(format!("{}m{}s", minutes, seconds as u64))
|
||||||
|
} else {
|
||||||
|
SerializableValue::Duration(format!("{}m{:.3}s", minutes, seconds))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if seconds.fract() == 0.0 {
|
||||||
|
SerializableValue::Duration(format!("{}s", seconds as u64))
|
||||||
|
} else {
|
||||||
|
SerializableValue::Duration(format!("{:.3}s", seconds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 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)),
|
Err(e) => Err(JsValue::from_str(&e)),
|
||||||
}
|
}
|
||||||
@@ -168,6 +265,32 @@ fn evaluate_cel_internal(expression: &str, context: &JsValue) -> Result<Value, S
|
|||||||
.map_err(|e| format!("Execution error: {}", e))
|
.map_err(|e| format!("Execution error: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract all identifiers referenced in a CEL expression
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn extract_cel_references(expression: &str) -> Result<js_sys::Array, JsValue> {
|
||||||
|
// Compile the CEL expression
|
||||||
|
let program = Program::compile(expression)
|
||||||
|
.map_err(|e| JsValue::from_str(&format!("Compilation error: {}", e)))?;
|
||||||
|
|
||||||
|
// Get references using the references() method
|
||||||
|
let references = program.references();
|
||||||
|
|
||||||
|
// Convert to JS array
|
||||||
|
let js_array = js_sys::Array::new();
|
||||||
|
|
||||||
|
// Convert each reference to a JsValue and push it to the JS array
|
||||||
|
for reference in references.variables().iter() {
|
||||||
|
js_array.push(&JsValue::from_str(reference));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also add function references if available
|
||||||
|
for function in references.functions().iter() {
|
||||||
|
js_array.push(&JsValue::from_str(&format!("function:{}", function)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(js_array)
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the WASM module (optional, for debugging)
|
/// Initialize the WASM module (optional, for debugging)
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user