Skip to content

Integration Guide

This guide covers how to use FiddlerScript from Rust code.

Creating an Interpreter

Standard Interpreter

The standard interpreter loads all OS environment variables as script variables:

use fiddler_script::Interpreter;

let mut interpreter = Interpreter::new();

Interpreter Without Environment Variables

For a clean environment without OS variables:

use fiddler_script::Interpreter;

let mut interpreter = Interpreter::new_without_env();

Interpreter with Custom Built-ins

Add custom built-in functions:

use fiddler_script::{Interpreter, Value, RuntimeError};
use std::collections::HashMap;

let mut builtins: HashMap<String, fn(Vec<Value>) -> Result<Value, RuntimeError>> = HashMap::new();

builtins.insert("double".to_string(), |args| {
    if let Some(Value::Integer(n)) = args.first() {
        Ok(Value::Integer(n * 2))
    } else {
        Err(RuntimeError::invalid_argument("Expected integer"))
    }
});

let mut interpreter = Interpreter::with_builtins(builtins);

Running Scripts

Basic Execution

use fiddler_script::Interpreter;

let mut interpreter = Interpreter::new();
let result = interpreter.run("let x = 10 + 5; x;");

match result {
    Ok(value) => println!("Result: {}", value),
    Err(e) => eprintln!("Error: {}", e),
}

Error Handling

FiddlerScript provides three error types wrapped in FiddlerError:

use fiddler_script::{Interpreter, FiddlerError, LexError, ParseError, RuntimeError};

let mut interpreter = Interpreter::new();
let result = interpreter.run(source);

match result {
    Ok(value) => println!("Success: {}", value),
    Err(FiddlerError::Lex(e)) => eprintln!("Lexer error: {}", e),
    Err(FiddlerError::Parse(e)) => eprintln!("Parser error: {}", e),
    Err(FiddlerError::Runtime(e)) => eprintln!("Runtime error: {}", e),
}

Injecting Variables

Setting Variables Before Execution

use fiddler_script::{Interpreter, Value};

let mut interpreter = Interpreter::new();

// Set different types
interpreter.set_variable_int("count", 42);
interpreter.set_variable_string("name", "Alice");
interpreter.set_variable_bytes("data", b"raw bytes".to_vec());
interpreter.set_variable_value("flag", Value::Boolean(true));

interpreter.run(r#"
    print("Count:", count);
    print("Name:", name);
    print("Data length:", len(data));
    print("Flag:", flag);
"#).unwrap();

Variable Setters

Method Description
set_variable_value(name, Value) Set any Value type
set_variable_int(name, i64) Set an integer
set_variable_string(name, impl Into<String>) Set a string
set_variable_bytes(name, Vec<u8>) Set bytes
set_variable_dict(name, IndexMap<String, Value>) Set a dictionary

Retrieving Variables

Getting Values After Execution

use fiddler_script::{Interpreter, Value};

let mut interpreter = Interpreter::new();
interpreter.run("let result = 10 * 5;").unwrap();

// Get as Value type
if let Some(value) = interpreter.get_value("result") {
    match value {
        Value::Integer(n) => println!("Integer: {}", n),
        Value::String(s) => println!("String: {}", s),
        Value::Boolean(b) => println!("Boolean: {}", b),
        Value::Bytes(bytes) => println!("Bytes: {} bytes", bytes.len()),
        Value::Array(arr) => println!("Array: {} elements", arr.len()),
        Value::Dictionary(dict) => println!("Dictionary: {} keys", dict.len()),
        Value::Null => println!("Null"),
    }
}

// Get as bytes (converts any value to bytes)
if let Some(bytes) = interpreter.get_bytes("result") {
    println!("As bytes: {:?}", bytes);
}

// Check if variable exists
if interpreter.has_variable("result") {
    println!("Variable exists");
}

Variable Getters

Method Returns Description
get_value(name) Option<Value> Get variable as Value type
get_bytes(name) Option<Vec<u8>> Get variable as bytes
has_variable(name) bool Check if variable exists

The Value Type

The Value enum represents all FiddlerScript values:

use indexmap::IndexMap;

pub enum Value {
    Integer(i64),
    String(String),
    Boolean(bool),
    Bytes(Vec<u8>),
    Array(Vec<Value>),
    Dictionary(IndexMap<String, Value>),
    Null,
}

Dictionaries use IndexMap to preserve insertion order when iterating over keys.

Converting Values to Bytes

All values can be converted to bytes:

use fiddler_script::Value;

let v = Value::Integer(42);
let bytes = v.to_bytes();  // "42" as bytes

let v = Value::String("hello".to_string());
let bytes = v.to_bytes();  // "hello" as bytes

let v = Value::Boolean(true);
let bytes = v.to_bytes();  // "true" as bytes

Complete Example

use fiddler_script::{Interpreter, Value};

fn process_message(message: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut interpreter = Interpreter::new();

    // Inject the message
    interpreter.set_variable_bytes("input", message.to_vec());
    interpreter.set_variable_int("timestamp", chrono::Utc::now().timestamp());

    // Run the processing script
    let script = r#"
        let text = bytes_to_string(input);
        let processed = text + " [processed at " + str(timestamp) + "]";
        let output = bytes(processed);
    "#;

    interpreter.run(script)?;

    // Get the result
    match interpreter.get_bytes("output") {
        Some(bytes) => Ok(bytes),
        None => Err("No output produced".into()),
    }
}

Reusing the Interpreter

The interpreter maintains state between run() calls, so you can define functions once and use them later:

use fiddler_script::Interpreter;

let mut interpreter = Interpreter::new();

// Define helper functions
interpreter.run(r#"
    fn double(x) { return x * 2; }
    fn triple(x) { return x * 3; }
"#).unwrap();

// Use them in subsequent calls
let result = interpreter.run("double(21);").unwrap();
println!("{}", result);  // 42

let result = interpreter.run("triple(14);").unwrap();
println!("{}", result);  // 42

Thread Safety

The Interpreter is not thread-safe. Each thread should have its own interpreter instance:

use fiddler_script::Interpreter;
use std::thread;

let handles: Vec<_> = (0..4).map(|i| {
    thread::spawn(move || {
        let mut interpreter = Interpreter::new();
        interpreter.set_variable_int("thread_id", i);
        interpreter.run("print(\"Thread\", thread_id);").unwrap();
    })
}).collect();

for handle in handles {
    handle.join().unwrap();
}