Compare commits
2 Commits
4973376639
...
8454550786
| Author | SHA1 | Date | |
|---|---|---|---|
|
8454550786
|
|||
|
695ad2e41e
|
12
index.html
12
index.html
@@ -199,6 +199,18 @@ name + " is " + string(age) + " years old and " + (active ? "active" : "inactive
|
||||
true}}}</code
|
||||
>
|
||||
</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>
|
||||
|
||||
<script type="module">
|
||||
|
||||
135
src/lib.rs
135
src/lib.rs
@@ -1,4 +1,5 @@
|
||||
use cel::{Context, Program, Value};
|
||||
use chrono::Duration as ChronoDuration;
|
||||
use js_sys;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -20,6 +21,59 @@ macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Parse an ISO 8601 timestamp string into a CEL Timestamp
|
||||
fn parse_timestamp(timestamp_str: &str) -> Result<Value, String> {
|
||||
Ok(Value::Timestamp(
|
||||
timestamp_str
|
||||
.parse()
|
||||
.map_err(|e| format!("Invalid timestamp format: {}", e))?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Parse a duration string (e.g., "1h30m", "45s", "2.5h") into a CEL Duration
|
||||
fn parse_duration(duration_str: &str) -> Result<Value, String> {
|
||||
// Parse duration strings like "1h30m45s", "2.5h", "300s", etc.
|
||||
let duration_str = duration_str.trim();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where string ends with a number (assume seconds)
|
||||
if !current_number.is_empty() {
|
||||
let value: f64 = current_number
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid number in duration: {}", current_number))?;
|
||||
total_seconds += value;
|
||||
}
|
||||
|
||||
let duration = ChronoDuration::seconds(total_seconds as i64);
|
||||
Ok(Value::Duration(duration))
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
@@ -34,7 +88,26 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
|
||||
Ok(Value::Float(n))
|
||||
}
|
||||
} 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() {
|
||||
// Handle arrays
|
||||
let array = js_sys::Array::from(js_val);
|
||||
@@ -70,7 +143,6 @@ fn js_value_to_cel_value(js_val: &JsValue) -> Result<Value, String> {
|
||||
}
|
||||
}
|
||||
|
||||
// Simple wrapper to make Value serializable for JS conversion
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum SerializableValue {
|
||||
@@ -81,6 +153,8 @@ enum SerializableValue {
|
||||
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),
|
||||
}
|
||||
|
||||
@@ -101,6 +175,37 @@ fn cel_value_to_serializable(cel_val: &Value) -> SerializableValue {
|
||||
.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)),
|
||||
}
|
||||
}
|
||||
@@ -160,6 +265,32 @@ fn evaluate_cel_internal(expression: &str, context: &JsValue) -> Result<Value, S
|
||||
.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)
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() {
|
||||
|
||||
Reference in New Issue
Block a user