use std::collections::HashMap;
use eryx::{CallbackError, DynamicCallback, Sandbox};
use serde_json::{Value, json};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("=== Runtime-Defined Callbacks Example ===\n");
println!("This example demonstrates creating callbacks dynamically at runtime,");
println!("where names, schemas, and behavior are all determined at runtime.\n");
let greet = DynamicCallback::builder("greet", "Greets a person by name", |args| {
Box::pin(async move {
let name = args
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| CallbackError::InvalidArguments("missing 'name'".into()))?;
let formal = args
.get("formal")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let greeting = if formal {
format!("Good day, {name}. How may I assist you?")
} else {
format!("Hey {name}! What's up?")
};
Ok(json!({ "greeting": greeting }))
})
})
.param("name", "string", "The person's name", true)
.param("formal", "boolean", "Use formal greeting", false)
.build();
let calculate = DynamicCallback::builder("calculate", "Performs a calculation", |args| {
Box::pin(async move {
let op = args
.get("operation")
.and_then(|v| v.as_str())
.ok_or_else(|| CallbackError::InvalidArguments("missing 'operation'".into()))?;
let a = args
.get("a")
.and_then(|v| v.as_f64())
.ok_or_else(|| CallbackError::InvalidArguments("missing 'a'".into()))?;
let b = args
.get("b")
.and_then(|v| v.as_f64())
.ok_or_else(|| CallbackError::InvalidArguments("missing 'b'".into()))?;
let result = match op {
"add" => a + b,
"sub" => a - b,
"mul" => a * b,
"div" => {
if b == 0.0 {
return Err(CallbackError::ExecutionFailed("division by zero".into()));
}
a / b
}
_ => {
return Err(CallbackError::InvalidArguments(format!(
"unknown operation: {op}"
)));
}
};
Ok(json!({ "result": result, "operation": op }))
})
})
.param(
"operation",
"string",
"The operation: add, sub, mul, div",
true,
)
.param("a", "number", "First operand", true)
.param("b", "number", "Second operand", true)
.build();
let data: HashMap<&str, Value> = [
("version", json!("1.0.0")),
("author", json!("Eryx Team")),
("features", json!(["sandbox", "callbacks", "async"])),
("config", json!({"debug": false, "timeout": 30})),
]
.into_iter()
.collect();
let lookup = DynamicCallback::builder(
"lookup",
"Looks up a value from a predefined dictionary",
move |args| {
let data = data.clone();
Box::pin(async move {
let key = args
.get("key")
.and_then(|v| v.as_str())
.ok_or_else(|| CallbackError::InvalidArguments("missing 'key'".into()))?;
let value = data.get(key).cloned().unwrap_or(Value::Null);
Ok(json!({ "key": key, "value": value, "found": !value.is_null() }))
})
},
)
.param("key", "string", "The key to look up", true)
.build();
println!("Created 3 dynamic callbacks: greet, calculate, lookup\n");
let sandbox = Sandbox::embedded()
.with_callback(greet)
.with_callback(calculate)
.with_callback(lookup)
.build()?;
println!(
"Sandbox created with {} runtime-defined callbacks\n",
sandbox.callbacks().len()
);
println!("=== Example 1: Dynamic 'greet' callback ===");
let result = sandbox
.execute(
r#"
# Use the dynamically-defined greet callback
informal = await greet(name="Alice")
print(f"Informal: {informal['greeting']}")
formal = await greet(name="Dr. Smith", formal=True)
print(f"Formal: {formal['greeting']}")
"#,
)
.await?;
println!("Output:\n{}", result.stdout);
println!("=== Example 2: Dynamic 'calculate' callback ===");
let result = sandbox
.execute(
r#"
# Perform various calculations using the dynamic callback
operations = [
("add", 10, 5),
("sub", 10, 5),
("mul", 10, 5),
("div", 10, 5),
]
for op, a, b in operations:
result = await calculate(operation=op, a=a, b=b)
print(f"{a} {op} {b} = {result['result']}")
"#,
)
.await?;
println!("Output:\n{}", result.stdout);
println!("=== Example 3: Dynamic 'lookup' callback ===");
let result = sandbox
.execute(
r#"
# Look up various keys
keys = ["version", "author", "features", "nonexistent"]
for key in keys:
result = await lookup(key=key)
if result['found']:
print(f"{key}: {result['value']}")
else:
print(f"{key}: NOT FOUND")
"#,
)
.await?;
println!("Output:\n{}", result.stdout);
println!("=== Example 4: Introspection ===");
let result = sandbox
.execute(
r#"
# List all available callbacks (including our dynamic ones)
callbacks = list_callbacks()
print(f"Available callbacks ({len(callbacks)}):")
for cb in callbacks:
print(f" - {cb['name']}: {cb['description']}")
# The schema is also available for introspection
schema = cb.get('parameters_schema', {})
if schema and 'properties' in schema:
props = schema['properties']
if props:
print(f" Parameters: {list(props.keys())}")
"#,
)
.await?;
println!("Output:\n{}", result.stdout);
println!("=== Example 5: Error handling ===");
let result = sandbox
.execute(
r#"
# Test error handling with dynamic callbacks
try:
result = await calculate(operation="div", a=10, b=0)
except Exception as e:
print(f"Division by zero caught: {e}")
try:
result = await calculate(operation="invalid_op", a=1, b=2)
except Exception as e:
print(f"Invalid operation caught: {e}")
"#,
)
.await?;
println!("Output:\n{}", result.stdout);
println!("\n=== Summary ===");
println!("DynamicCallback is useful when:");
println!(" - Callback definitions come from configuration files");
println!(" - You're building a plugin system");
println!(" - API endpoints are discovered dynamically");
println!(" - You need to proxy calls to external services");
println!("\nFor compile-time type safety, prefer TypedCallback.");
println!("For maximum flexibility, use DynamicCallback or the base Callback trait.");
Ok(())
}