use std::future::Future;
use std::pin::Pin;
use eryx::JsonSchema;
use eryx::{CallbackError, Sandbox, TypedCallback};
use serde::Deserialize;
use serde_json::{Value, json};
#[derive(Deserialize, JsonSchema)]
struct FailArgs {
message: String,
}
struct FailingCallback;
impl TypedCallback for FailingCallback {
type Args = FailArgs;
fn name(&self) -> &str {
"fail"
}
fn description(&self) -> &str {
"A callback that always returns an error"
}
fn invoke_typed(
&self,
args: FailArgs,
) -> Pin<Box<dyn Future<Output = Result<Value, CallbackError>> + Send + '_>> {
Box::pin(async move { Err(CallbackError::ExecutionFailed(args.message)) })
}
}
#[derive(Deserialize, JsonSchema)]
struct ValidateArgs {
value: i64,
}
struct ValidatingCallback;
impl TypedCallback for ValidatingCallback {
type Args = ValidateArgs;
fn name(&self) -> &str {
"validate"
}
fn description(&self) -> &str {
"A callback that validates its arguments strictly"
}
fn invoke_typed(
&self,
args: ValidateArgs,
) -> Pin<Box<dyn Future<Output = Result<Value, CallbackError>> + Send + '_>> {
Box::pin(async move {
if args.value < 0 {
return Err(CallbackError::InvalidArguments(
"value must be non-negative".into(),
));
}
if args.value > 100 {
return Err(CallbackError::InvalidArguments(
"value must not exceed 100".into(),
));
}
Ok(json!({ "validated": args.value }))
})
}
}
async fn run_python_error_examples(sandbox: &Sandbox) -> anyhow::Result<()> {
println!("=== Example 1: Python syntax error ===");
let result = sandbox
.execute(
"
def broken(
# Missing closing parenthesis and colon
print('never reached')
",
)
.await;
match result {
Ok(_) => println!("Unexpected success!"),
Err(e) => println!("Error (expected): {e}"),
}
println!();
println!("=== Example 2: Python runtime error (undefined variable) ===");
let result = sandbox
.execute(
"
x = 10
y = x + undefined_variable
",
)
.await;
match result {
Ok(_) => println!("Unexpected success!"),
Err(e) => println!("Error (expected): {e}"),
}
println!();
println!("=== Example 3: Python division by zero ===");
let result = sandbox
.execute(
"
result = 42 / 0
",
)
.await;
match result {
Ok(_) => println!("Unexpected success!"),
Err(e) => println!("Error (expected): {e}"),
}
println!();
println!("=== Example 7: Python type error ===");
let result = sandbox
.execute(
"
x = 'hello'
y = x + 42 # Can't concatenate str and int
",
)
.await;
match result {
Ok(_) => println!("Unexpected success!"),
Err(e) => println!("Error (expected): {e}"),
}
println!();
Ok(())
}
async fn run_callback_error_examples(sandbox: &Sandbox) -> anyhow::Result<()> {
println!("=== Example 4: Callback that returns an error ===");
let result = sandbox
.execute(
r#"
try:
result = await fail(message="Something went wrong!")
print(f"Result: {result}")
except Exception as e:
print(f"Caught exception in Python: {e}")
"#,
)
.await;
match result {
Ok(r) => println!("Output: {}", r.stdout),
Err(e) => println!("Error: {e}"),
}
println!();
println!("=== Example 5: Callback argument validation ===");
let result = sandbox
.execute(
r#"
# Try valid value
result = await validate(value=50)
print(f"Valid (50): {result}")
# Try boundary values
result = await validate(value=0)
print(f"Valid (0): {result}")
result = await validate(value=100)
print(f"Valid (100): {result}")
# Try invalid value (will raise exception)
try:
result = await validate(value=150)
print(f"Invalid (150): {result}")
except Exception as e:
print(f"Validation error for 150: {e}")
# Try missing argument
try:
result = await validate()
print(f"Missing arg: {result}")
except Exception as e:
print(f"Validation error for missing arg: {e}")
"#,
)
.await;
match result {
Ok(r) => {
println!("Output:");
for line in r.stdout.lines() {
println!(" {line}");
}
}
Err(e) => println!("Error: {e}"),
}
println!();
println!("=== Example 6: Non-existent callback ===");
let result = sandbox
.execute(
r#"
try:
# Use invoke() for dynamic/unknown callback names
result = await invoke("nonexistent_callback")
print(f"Result: {result}")
except Exception as e:
print(f"Error calling nonexistent callback: {e}")
"#,
)
.await;
match result {
Ok(r) => println!("Output: {}", r.stdout),
Err(e) => println!("Error: {e}"),
}
println!();
Ok(())
}
async fn run_graceful_error_handling_example(sandbox: &Sandbox) -> anyhow::Result<()> {
println!("=== Example 8: Graceful error handling pattern ===");
let result = sandbox
.execute(
r#"
async def safe_invoke(name, **kwargs):
"""Wrapper that catches callback errors and returns None."""
try:
return await invoke(name, **kwargs)
except Exception as e:
print(f"Warning: {name} failed with {e}")
return None
# This pattern allows code to continue even if some callbacks fail
results = []
for i, value in enumerate([50, 150, 75, -10, 25]):
result = await safe_invoke("validate", value=value)
if result is not None:
results.append(result)
print(f" Value {value}: OK")
else:
print(f" Value {value}: FAILED")
print(f"\nSuccessfully validated {len(results)} out of 5 values")
"#,
)
.await;
match result {
Ok(r) => {
println!("Output:");
for line in r.stdout.lines() {
println!(" {line}");
}
}
Err(e) => println!("Error: {e}"),
}
println!();
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let sandbox = Sandbox::embedded()
.with_callback(FailingCallback)
.with_callback(ValidatingCallback)
.build()?;
println!("Sandbox created");
println!();
run_python_error_examples(&sandbox).await?;
run_callback_error_examples(&sandbox).await?;
run_graceful_error_handling_example(&sandbox).await?;
println!("Error handling examples completed!");
Ok(())
}