Skip to main content
Apicentric includes a scripting engine powered by Rhai, a lightweight embedded scripting language for Rust. You can use Rhai scripts to add dynamic behavior to your simulated APIs based on request context.
Scripting is enabled by default through the scripting feature flag. Scripts are compiled once and cached for performance.

Basic scripting

Rhai scripts have access to a context object (ctx) that contains request information. Scripts can perform calculations, make decisions, and return values that influence the response.

Simple calculation

let x = 10;
let y = 20;
x + y
The last expression in a script is automatically returned.

Accessing request context

ctx.request.method
This returns the HTTP method (e.g., “GET”, “POST”) from the current request.

Context structure

The ctx object provides access to request data:
// HTTP method
ctx.request.method

// Request path
ctx.request.path

// Query parameters
ctx.request.query.category

// Headers
ctx.request.headers["authorization"]

// Request body (if present)
ctx.request.body

Built-in functions

The scripting engine registers several helper functions:
log("This is a log message");
console_log("This appears in the console");
print("Debug output");

Script execution

Scripts are executed within the simulator using the ScriptingEngine:
use apicentric::simulator::scripting::ScriptingEngine;
use serde_json::json;

let engine = ScriptingEngine::new();

let script = r#"
    let method = ctx.request.method;
    if method == "POST" {
        "created"
    } else {
        "retrieved"
    }
"#;

let context = json!({
    "request": {
        "method": "POST"
    }
});

let result = engine.execute(script, &context).unwrap();
assert_eq!(result, "created");

Performance optimization

Script caching

The scripting engine automatically caches compiled scripts:
  1. First execution: Script is compiled and cached
  2. Subsequent executions: Cached AST is reused
This provides significant performance benefits for repeated script execution.

Thread safety

The ScriptingEngine is thread-safe and can be shared across multiple requests:
use std::sync::Arc;

let engine = Arc::new(ScriptingEngine::new());
// Clone the Arc and use in multiple threads

Conditional logic

if ctx.request.method == "POST" {
    log("Creating new resource");
    201
} else if ctx.request.method == "GET" {
    log("Retrieving resource");
    200
} else {
    405
}

Working with data

let items = [1, 2, 3, 4, 5];
let sum = 0;

for item in items {
    sum += item;
}

sum  // Returns 15

Return values

Scripts can return various types:
  • Numbers: 42, 3.14
  • Strings: "hello"
  • Booleans: true, false
  • Objects: #{ key: value }
  • Arrays: [1, 2, 3]
The return value is automatically converted to JSON:
#{
    status: "success",
    timestamp: now(),
    method: ctx.request.method
}

Error handling

If a script fails to compile or execute, an error is returned:
let result = engine.execute("invalid syntax!!!", &context);
assert!(result.is_err());
Script errors should be handled gracefully in production. Consider returning a default value or error response when scripts fail.

Feature flag

Scripting is controlled by the scripting feature flag:
Cargo.toml
[dependencies]
apicentric = { version = "*", features = ["scripting"] }
When scripting is disabled, execute() always returns null:
#[cfg(not(feature = "scripting"))]
let result = engine.execute(script, &context);
assert_eq!(result, Value::Null);

Practical examples

Dynamic status codes

if ctx.request.headers["x-test"] == "true" {
    // Return 503 for test traffic
    503
} else {
    200
}

Request validation

let body = ctx.request.body;

if body.customer_id == () {
    log("Missing customer_id");
    #{
        error: "validation_error",
        message: "customer_id is required"
    }
} else {
    #{
        status: "valid"
    }
}

Time-based behavior

let current_time = now();
log("Request received at: " + current_time);

#{
    timestamp: current_time,
    status: "processed"
}

Rhai language features

Rhai is a full-featured scripting language with:
  • Variables: let x = 10;
  • Functions: fn add(a, b) { a + b }
  • Loops: for, while, loop
  • Conditionals: if, else, switch
  • Operators: +, -, *, /, %, ==, !=, >, <
  • Arrays: [1, 2, 3]
  • Objects: #{ key: value }
  • Closures: |x| x * 2
For more advanced Rhai features and syntax, visit the official Rhai documentation.

Best practices

  • Keep scripts simple - Complex logic belongs in plugins or application code
  • Use logging - Debug scripts with log() statements
  • Test scripts - Verify script behavior with different contexts
  • Cache awareness - Scripts are cached by their string content
  • Type safety - Rhai is dynamically typed; validate inputs
  • Performance - Avoid expensive operations in frequently-executed scripts

Debugging scripts

log("Script started");
log("Method: " + ctx.request.method);
log("Path: " + ctx.request.path);

let result = ctx.request.method;
log("Returning: " + result);

result
Log messages appear in the console output with the prefix “Script log:” or “Script console:”.

Limitations

  • No access to filesystem or network
  • No access to external crates or libraries
  • Limited to registered functions and context data
  • Scripts run synchronously (blocking)
  • No sandboxing beyond Rhai’s built-in safety
Rhai is designed to be safe and sandboxed. Scripts cannot access the filesystem, network, or unsafe Rust operations.

Build docs developers (and LLMs) love