allow including context
https://claude.ai/chat/a6df04ed-85dd-46b9-a3bd-26b5ae3c78f5
This commit is contained in:
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -91,9 +91,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cel_js_example"
|
||||
version = "0.0.0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cel",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -112,6 +113,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@@ -287,6 +294,12 @@ version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -313,6 +326,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
|
||||
@@ -3,10 +3,11 @@ description = "Example of running cel-rust interpreter in browser"
|
||||
edition = "2024"
|
||||
name = "cel_js_example"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
cel = "0.11.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
126
index.html
126
index.html
@@ -3,20 +3,72 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Starlark evaluator</title>
|
||||
<title>CEL evaluator</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: calc(100% - 20px);
|
||||
height: 20em;
|
||||
width: 100%;
|
||||
height: 15em;
|
||||
box-sizing: border-box;
|
||||
margin: 5px;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
border-color: lightgray;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
border: 2px solid lightgray;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#output {
|
||||
background-color: #f8f9fa;
|
||||
height: 10em;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #d32f2f;
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #2e7d32;
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1976d2;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1976d2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -39,7 +91,6 @@
|
||||
const writeString = (s) => {
|
||||
const encoded = new TextEncoder().encode(s.trim());
|
||||
const offset = instance.exports.allocation(4 + encoded.byteLength);
|
||||
// TODO(april): this probably isn't guaranteed to be 4-byte aligned? Might need to fix.
|
||||
const memory = instance.exports.memory.buffer;
|
||||
const uint32s = new Uint32Array(memory, offset, 1);
|
||||
uint32s[0] = encoded.byteLength;
|
||||
@@ -48,29 +99,62 @@
|
||||
return offset;
|
||||
};
|
||||
|
||||
const content = document.getElementById("input").value;
|
||||
const offset = instance.exports.evaluate(writeString(content));
|
||||
// Combine CEL expression and JSON context
|
||||
const expression = document.getElementById("input").value;
|
||||
const context = document.getElementById("context").value;
|
||||
|
||||
// Create a combined input with expression and context separated by a delimiter
|
||||
const combinedInput = expression + "\n---CONTEXT---\n" + context;
|
||||
|
||||
const offset = instance.exports.evaluate(writeString(combinedInput));
|
||||
const ok = readU8(offset) != 0;
|
||||
const result = readString(offset + 4);
|
||||
const output = document.getElementById("output");
|
||||
output.value = (ok ? "" : "ERROR\n") + result;
|
||||
|
||||
// Style the output based on success/error
|
||||
if (ok) {
|
||||
output.className = "success";
|
||||
output.value = result;
|
||||
} else {
|
||||
output.className = "error";
|
||||
output.value = "ERROR\n" + result;
|
||||
}
|
||||
}).catch(error => {
|
||||
const output = document.getElementById("output");
|
||||
output.className = "error";
|
||||
output.value = "WASM Loading Error: " + error.message;
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
document.getElementById("input").addEventListener("input", run, false);
|
||||
run()
|
||||
})
|
||||
document.getElementById("context").addEventListener("input", run, false);
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
<h1>Starlark evaluator</h1>
|
||||
|
||||
<h1>CEL evaluator</h1>
|
||||
<p>
|
||||
Using <a href="https://github.com/cel-rust/cel-rust">cel-rust</a> compiled to web assembly.
|
||||
Change the input to see it update.
|
||||
Using <a href="https://github.com/cel-rust/cel-rust">cel-rust</a> compiled to WebAssembly.
|
||||
Change the CEL expression or JSON context to see it update in real-time.
|
||||
</p>
|
||||
<textarea id="input">
|
||||
"Hello " + "world!"</textarea>
|
||||
<textarea id="output" readonly="readonly" style="background-color: lightgray;">
|
||||
</textarea>
|
||||
|
||||
<div class="container">
|
||||
<div>
|
||||
<label for="input">CEL Expression:</label>
|
||||
<textarea id="input" placeholder="Enter your CEL expression here...">name + " is " + string(age) + " years old"</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="context">JSON Context:</label>
|
||||
<textarea id="context" placeholder="Enter JSON context here...">{"name": "Alice", "age": 30, "active": true}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label for="output">Result:</label>
|
||||
<textarea id="output" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
85
src/lib.rs
85
src/lib.rs
@@ -1,8 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
|
||||
use cel::{Context, ExecutionError, Program, Value};
|
||||
use cel::{Context, Program, Value};
|
||||
use serde_json;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn allocation(n: usize) -> *mut u8 {
|
||||
@@ -21,9 +23,9 @@ pub unsafe extern "C" fn evaluate(s: *const u8) -> *mut u8 {
|
||||
|
||||
fn evaluate_buffers(input: &[u8]) -> Vec<u8> {
|
||||
let contents = str::from_utf8(input).unwrap();
|
||||
let result = evaluate_cel(contents);
|
||||
let result = evaluate_cel_with_context(contents);
|
||||
let success = result.is_ok();
|
||||
let message = result.unwrap_or_else(|e| e.to_string());
|
||||
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 });
|
||||
@@ -33,10 +35,75 @@ fn evaluate_buffers(input: &[u8]) -> Vec<u8> {
|
||||
buffer
|
||||
}
|
||||
|
||||
fn evaluate_cel(content: &str) -> Result<String, ExecutionError> {
|
||||
let program = Program::compile(content).unwrap();
|
||||
let mut context = Context::default();
|
||||
// context.add_function("add", |a: i64, b: i64| a + b);
|
||||
let value = program.execute(&context)?;
|
||||
Ok(format!("{:?}", value))
|
||||
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
|
||||
}
|
||||
}
|
||||
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()
|
||||
} else {
|
||||
"{}"
|
||||
};
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(format!("JSON parsing error: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the program
|
||||
match program.execute(&context) {
|
||||
Ok(value) => Ok(format!("{:?}", value)),
|
||||
Err(e) => Err(format!("Execution error: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user