switch to wasm-bindgen
This commit is contained in:
436
index.html
436
index.html
@@ -1,160 +1,320 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
|
||||
<head>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>CEL evaluator</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
<title>CEL-Rust WASM-bindgen Example</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 15em;
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
border: 2px solid lightgray;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
}
|
||||
textarea,
|
||||
input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#output {
|
||||
background-color: #f8f9fa;
|
||||
height: 10em;
|
||||
}
|
||||
textarea {
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
}
|
||||
.result {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #28a745;
|
||||
white-space: pre-wrap;
|
||||
font-family: "Courier New", monospace;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #d32f2f;
|
||||
background-color: #ffebee;
|
||||
}
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
border-color: #dc3545;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #2e7d32;
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
border-color: #28a745;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1976d2;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1976d2;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.examples {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.example {
|
||||
background-color: #f1f3f4;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.example:hover {
|
||||
background-color: #e8eaed;
|
||||
}
|
||||
|
||||
.example h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.example code {
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
function run() {
|
||||
WebAssembly.instantiateStreaming(fetch("cel_js_example.wasm"), {}).then(({ instance }) => {
|
||||
const readString = (offset) => {
|
||||
const memory = instance.exports.memory.buffer;
|
||||
const length = new Uint32Array(memory, offset, 1)[0];
|
||||
const characters = new Uint8Array(memory, offset + 4, length);
|
||||
return new TextDecoder().decode(characters);
|
||||
};
|
||||
|
||||
const readU8 = (offset) => {
|
||||
return new Uint8Array(instance.exports.memory.buffer, offset, 1)[0];
|
||||
};
|
||||
|
||||
const writeString = (s) => {
|
||||
const encoded = new TextEncoder().encode(s.trim());
|
||||
const offset = instance.exports.allocation(4 + encoded.byteLength);
|
||||
const memory = instance.exports.memory.buffer;
|
||||
const uint32s = new Uint32Array(memory, offset, 1);
|
||||
uint32s[0] = encoded.byteLength;
|
||||
const uint8s = new Uint8Array(memory, offset + 4, encoded.byteLength);
|
||||
uint8s.set(encoded);
|
||||
return offset;
|
||||
};
|
||||
|
||||
// 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");
|
||||
|
||||
// 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);
|
||||
document.getElementById("context").addEventListener("input", run, false);
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>CEL evaluator</h1>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CEL-Rust WASM-bindgen Demo</h1>
|
||||
<p>
|
||||
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.
|
||||
This demo uses <code>wasm-bindgen</code> to provide a clean JavaScript API
|
||||
for CEL evaluation.
|
||||
</p>
|
||||
|
||||
<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>
|
||||
<label for="expression">CEL Expression:</label>
|
||||
<textarea id="expression" placeholder="Enter CEL expression...">
|
||||
name + " is " + string(age) + " years old and " + (active ? "active" : "inactive")</textarea
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="context">Context (as JavaScript object):</label>
|
||||
<textarea
|
||||
id="context"
|
||||
placeholder="Enter context as JavaScript object..."
|
||||
>
|
||||
{"name": "Alice", "age": 30, "active": true, "scores": [95, 87, 92], "profile": {"country": "US", "level": "senior"}}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label for="output">Result:</label>
|
||||
<textarea id="output" readonly="readonly"></textarea>
|
||||
<button onclick="evaluate()">Evaluate CEL Expression</button>
|
||||
<button onclick="evaluateAsJs()">Evaluate & Return as JS Value</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div class="full-width">
|
||||
<label>Result:</label>
|
||||
<div id="result" class="result">Click "Evaluate" to see results...</div>
|
||||
</div>
|
||||
|
||||
<div class="examples">
|
||||
<h2>Examples (click to try):</h2>
|
||||
|
||||
<div class="example" onclick="loadExample(this)">
|
||||
<h4>Basic String Operations</h4>
|
||||
<code
|
||||
data-expression="'Hello ' + name + '!'"
|
||||
data-context='{"name": "World"}'
|
||||
>Expression: 'Hello ' + name + '!'</code
|
||||
>
|
||||
<code>Context: {"name": "World"}</code>
|
||||
</div>
|
||||
|
||||
<div class="example" onclick="loadExample(this)">
|
||||
<h4>Math Operations</h4>
|
||||
<code
|
||||
data-expression="(price * quantity) * (1 + tax)"
|
||||
data-context='{"price": 10.50, "quantity": 3, "tax": 0.08}'
|
||||
>Expression: (price * quantity) * (1 + tax)</code
|
||||
>
|
||||
<code>Context: {"price": 10.50, "quantity": 3, "tax": 0.08}</code>
|
||||
</div>
|
||||
|
||||
<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-context='{"scores": [85, 92, 78, 96, 88]}'
|
||||
>Expression: scores.filter(s, s > 90).size() + ' high scores'</code
|
||||
>
|
||||
<code>Context: {"scores": [85, 92, 78, 96, 88]}</code>
|
||||
</div>
|
||||
|
||||
<div class="example" onclick="loadExample(this)">
|
||||
<h4>Conditional Logic</h4>
|
||||
<code
|
||||
data-expression="age >= 18 ? (age >= 65 ? 'senior' : 'adult') : 'minor'"
|
||||
data-context='{"age": 25}'
|
||||
>Expression: age >= 18 ? (age >= 65 ? 'senior' : 'adult') :
|
||||
'minor'</code
|
||||
>
|
||||
<code>Context: {"age": 25}</code>
|
||||
</div>
|
||||
|
||||
<div class="example" onclick="loadExample(this)">
|
||||
<h4>Map/Object Access</h4>
|
||||
<code
|
||||
data-expression="user.profile.country == 'US' && user.profile.verified"
|
||||
data-context='{"user": {"profile": {"country": "US", "verified": true}}}'
|
||||
>Expression: user.profile.country == 'US' &&
|
||||
user.profile.verified</code
|
||||
>
|
||||
<code
|
||||
>Context: {"user": {"profile": {"country": "US", "verified":
|
||||
true}}}</code
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import init, {
|
||||
evaluate_cel,
|
||||
evaluate_cel_js,
|
||||
} from "./pkg/cel_rust_wasm.js";
|
||||
|
||||
let wasmModule;
|
||||
|
||||
async function initWasm() {
|
||||
wasmModule = await init();
|
||||
console.log("WASM module loaded successfully");
|
||||
|
||||
// Make functions globally available
|
||||
window.evaluate_cel = evaluate_cel;
|
||||
window.evaluate_cel_js = evaluate_cel_js;
|
||||
|
||||
// Enable the evaluate button
|
||||
document.querySelector("button").disabled = false;
|
||||
}
|
||||
|
||||
// Initialize WASM when the page loads
|
||||
initWasm().catch(console.error);
|
||||
|
||||
// Make functions globally available for onclick handlers
|
||||
window.evaluate = evaluate;
|
||||
window.evaluateAsJs = evaluateAsJs;
|
||||
window.loadExample = loadExample;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function evaluate() {
|
||||
if (!window.evaluate_cel) {
|
||||
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(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 evaluateAsJs() {
|
||||
if (!window.evaluate_cel_js) {
|
||||
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 JavaScript value result version
|
||||
const result = window.evaluate_cel_js(expression, context);
|
||||
|
||||
resultDiv.className = "result success";
|
||||
resultDiv.textContent = `Success (JS Value): ${JSON.stringify(result, null, 2)}`;
|
||||
|
||||
// Log the actual JavaScript object to console
|
||||
console.log("CEL Result as JavaScript value:", result);
|
||||
} catch (e) {
|
||||
resultDiv.className = "result error";
|
||||
resultDiv.textContent = `Error: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
function loadExample(element) {
|
||||
const codeElement = element.querySelector("code[data-expression]");
|
||||
const expression = codeElement.getAttribute("data-expression");
|
||||
const context = codeElement.getAttribute("data-context");
|
||||
|
||||
document.getElementById("expression").value = expression;
|
||||
document.getElementById("context").value = context;
|
||||
|
||||
// Auto-evaluate
|
||||
setTimeout(evaluate, 100);
|
||||
}
|
||||
|
||||
// Auto-evaluate on input change
|
||||
document.getElementById("expression").addEventListener("input", () => {
|
||||
setTimeout(evaluate, 300);
|
||||
});
|
||||
|
||||
document.getElementById("context").addEventListener("input", () => {
|
||||
setTimeout(evaluate, 300);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user