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
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" );
Script caching
The scripting engine automatically caches compiled scripts:
First execution: Script is compiled and cached
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
Arrays
Objects
String manipulation
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:
[ 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
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.