Rustman scripting language
Pre-request and test scripts run on rustman-engine, a small interpreted
language written from scratch in Rust — not JavaScript. There is no pm.* API,
no Node builtins, no require/import. It's deliberately tiny: the
target audience for actually typing this syntax is an AI generating a script from a
plain-English request, not a person memorizing a grammar. This page is a complete
reference — every construct that exists is listed below, and nothing else exists at
runtime.
Global scripts
The Settings panel has a Global Scripts section — a pre-request/test script pair that runs for every request, so common setup (an auth header every request needs, environment bootstrapping) doesn't need copy-pasting into each request's own Scripts tab.
The global script always runs first, then the request's own script runs second and can see
(and override) anything the global script already did — its set_header/set_body
effects are folded into what header()/body() return inside the
per-request script. Test results and print(...) logs from both accumulate in
that order. If either script fails, that phase's error is labeled so it's clear which one
broke; a pre-request failure stops the request from sending at all.
Where a script runs and what it can see
| Pre-request script | Test script | |
|---|---|---|
| Runs | Before the request is sent | After the response is received |
| env(...) | Active environment's variables | |
| header(...) | The request's own headers | The response's headers |
| cookie(...) | The request's cookie jar | |
| response | Not available — doesn't exist yet | Available — status + body |
| body(), url() | The request's own body/URL | Same — what was actually sent (not the response) |
| headers() | All of the request's own headers | All of the response's headers |
| Effects that matter | set_header, set_body, set_env | test, set_env |
A pre-request script's set_header calls are merged into the outgoing
request's headers, and a set_body call replaces the outgoing body.
Neither touches what's saved in the request editor — only the one outgoing
request is affected. A test script's test calls populate the response's
Tests tab. set_env works in either script and writes to the
active environment, persisted immediately — same as editing it by hand.
set_header/test calls are applied.
Syntax
Statements
let name = <expr> if <expr> { <statements> } if <expr> { <statements> } else { <statements> } <expr>
A trailing ; is allowed but optional. There are no loops
(for/while) and no user-defined functions — scripts are a short,
flat (plus if/else) sequence of statements.
Comments & literals
// rest of line— no block comments.- Numbers:
42,3.14— always floating point.-5is not valid syntax (no unary minus for numbers); write0 - 5. - Strings:
"double-quoted", escapes\n\t\"\\. - Booleans:
true,false. Andnull.
Variables
let x = <expr> binds x for the rest of the script. There's
no block scoping — everything is one flat scope, including names bound inside an
if/else branch.
Operators, highest to lowest precedence
| # | Operators | Notes |
|---|---|---|
| 1 | .field / .method() / fn(args) | Field access and calls |
| 2 | ! | Logical not (prefix) |
| 3 | + - | Add/subtract, see string concatenation below |
| 4 | < > <= >= | Numeric comparison only — runtime error on non-numbers |
| 5 | == != | Equality — different types are always unequal, never coerced |
| 6 | && | Logical and, short-circuits |
| 7 | || | Logical or, short-circuits |
Parentheses ( <expr> ) group as usual.
String concatenation
+ on two numbers adds them. If either side isn't a number,
it falls back to string concatenation, so "Bearer " + token and
"count: " + 3 both just work — there's no separate concat operator.
Field access
value.field reads a field off an object (from jwt_decode(...),
json_parse(...), or response.json()). A missing field, or a
field access on a non-object, evaluates to null — not an error. This is a
different convention from env()/header()/cookie()
below: JSON payload shape is expected to vary, so a missing field is a normal, silent
null; a missing env var or header instead comes back as "",
because scripts commonly branch on it with != "".
The response object (test scripts only)
response.status— a number, the HTTP status code.response.json()— parses the body as JSON into a value tree you can field-access into;nullif the body isn't valid JSON.response.text()— the raw response body as a string.
There's no response.headers field — read response headers with the
header(...) builtin, which is fed response headers specifically in test
scripts so the same name works in both script slots.
Built-in functions
| Function | Signature | Notes |
|---|---|---|
| env(name) | (string) → string | Active environment variable. "" if unset, not null. |
| set_env(name, value) | (string, any) → null | Writes the active environment variable (effect, persisted). value can be any type — objects/arrays are JSON-stringified, everything else uses its display form. |
| header(name) | (string) → string | Case-insensitive. Request headers pre-request, response headers in a test script. "" if absent. |
| headers() | () → object | Every header at once, same request-vs-response split as header(name) — for dumping everything instead of naming each one. |
| set_header(name, value) | (string, any) → null | Adds/overrides a request header. value can be any type, same auto-conversion as set_env — set_header("X-User-Detail", claims.payload) works directly. |
| cookie(name) | (string) → string | Case-insensitive lookup on the request's cookie jar. "" if absent. |
| body() | () → string | The request's own body — available in both script slots (debugging what was actually sent). Use response.text()/response.json() for what came back. |
| set_body(value) | (any) → null | Replaces the outgoing request body (pre-request scripts only). Same auto-conversion as set_env/set_header. |
| url() | () → string | The request's own URL — available in both script slots. |
| test(name, condition) | (string, bool) → null | Records a pass/fail on the response's Tests tab. |
| print(value) | (any) → null | Logs value (auto-converted like set_env) to the Tests tab's console section — for debugging without an assertion. |
| base64_encode(s) | (string) → string | Standard base64 (not URL-safe). |
| base64_decode(s) | (string) → string | Accepts standard or URL-safe-no-pad base64. Null if invalid or not UTF-8 once decoded. |
| jwt_decode(token) | (string) → object | No signature verification — claims only. Accepts a bare token or one prefixed with "Bearer ". Returns { header, payload }, or null if malformed. |
| json_parse(s) | (string) → value | Parses arbitrary JSON text. Null on invalid JSON. |
| json_stringify(value) | (value) → string | Serializes any value back into real, properly-quoted JSON — the inverse of json_parse/jwt_decode's output. |
| aes_encrypt(text, key) | (string, string) → string | AES-256-GCM, keyed by the SHA-256 hash of key (any string works, any length). Returns base64(nonce || ciphertext). |
| aes_decrypt(text, key) | (string, string) → string | Reverses aes_encrypt with the same key. Null if the key is wrong or input is malformed — fails closed, not open. |
Truthiness (used by if, !, &&,
||, and test's second argument): null and
false are falsy; 0 is falsy, any other number truthy;
"" is falsy, any other string truthy; an empty array is falsy; objects are
always truthy.
aes_encrypt/aes_decrypt round-trip with each other, but the exact
framing (SHA-256 key derivation, nonce || ciphertext layout) is this engine's
own convention — it's not guaranteed to match another system's AES-GCM
implementation. If an external API dictates its own key derivation or nonce
handling, treat these as a starting point, not a drop-in match.
Worked examples
Inject a bearer token, only if one is set
let token = env("access_token") if token != "" { set_header("Authorization", "Bearer " + token) }
Decode a JWT from the response and stash claims
let claims = jwt_decode(header("Authorization")) set_env("user_id", claims.payload.sub) set_env("access_token", claims.payload.access_token) test("status is 200", response.status == 200) test("has user id", claims.payload.sub != null)
Assert on the JSON response body
let body = response.json() test("id is 7", body.id == 7) test("name matches", body.name == "ferris")
Range check with if/else
let status = response.status if status >= 200 && status < 300 { test("ok range", true) } else { test("ok range", false) }
Check a cookie was set
test("has session", cookie("session_id") != "")
Forward a decoded JWT cookie's claims as a header
A common gateway/BFF pattern: a session JWT lives in a cookie, and the backend wants the decoded user details forwarded as a single header rather than re-decoding the JWT itself.
let claims = jwt_decode(cookie("accessToken")) set_header("X-User-Detail", claims.payload)
set_header auto-converts a non-string value, so passing the object directly
JSON-stringifies it the same as calling json_stringify(claims.payload) by hand.
If only one field is actually needed, send just that field — it's already a string:
let claims = jwt_decode(cookie("accessToken")) set_header("X-User-Detail", claims.payload.masterPhrId)
Debug what a request actually sent, from its own test script
print(url()) print(headers()) print(body())
Encrypt the outgoing body, decrypt the response to assert on it
// pre-request script let plaintext = body() set_body(aes_encrypt(plaintext, env("body_key")))
// test script let plaintext = aes_decrypt(response.text(), env("body_key")) let parsed = json_parse(plaintext) test("status is 200", response.status == 200) test("order id present", parsed.order_id != null)
What this language deliberately does not have
No loops, no user-defined functions, no array/object literals (you can only
receive arrays/objects from jwt_decode/json_parse/response.json(),
not construct them), no try/catch (a runtime error just aborts
the script), no string indexing/slicing, no regex. If a task needs any of these, it's out
of scope for a script — solve it with headers/env vars/assertions in the shapes above
instead of reaching for a workaround.
Prompt for an LLM
Don't hand-write these scripts — copy the block below into ChatGPT, Claude, or any other LLM, add your own request at the end, and let it write the script for you.
You write scripts in Rustman's scripting language: a small custom language
(not JavaScript, no pm.* API). Only use what's listed below — nothing else
exists at runtime, and inventing syntax or functions will just fail.
STATEMENTS: `let x = <expr>`, `if <expr> { ... }`, `if <expr> { ... } else { ... }`,
or a bare expression. Trailing `;` optional. No loops, no user-defined functions.
LITERALS: numbers (no negative literals — write `0 - 5`, not `-5`), "double-quoted
strings" (\n \t \" \\ escapes), true, false, null.
OPERATORS, low to high precedence: || , && , == != , < > <= >= (numbers only) ,
+ - (numbers add; if either side isn't a number, + concatenates as text) ,
! (prefix not) , .field / .method() / call(...).
COMMENTS: `// rest of line`.
BUILT-IN FUNCTIONS:
env(name) -> string ("" if unset)
set_env(name, value)
header(name) -> string ("" if absent; request headers pre-request,
response headers in a test script)
headers() -> object (every header at once, same split as header())
set_header(name, value)
cookie(name) -> string ("" if absent)
body() -> string (the request's own body; works in both script slots)
set_body(value) (pre-request scripts only, replaces the
outgoing body)
url() -> string (the request's own URL; works in both script slots)
test(name, condition) (records a pass/fail)
print(value) (debug log, no assertion)
base64_encode(s) / base64_decode(s)
jwt_decode(token) -> {header, payload} (no signature check; accepts a bare
token or "Bearer <token>")
json_parse(s) -> value
json_stringify(value) -> string
aes_encrypt(text, key) / aes_decrypt(text, key) (AES-256-GCM, key is any
string, SHA-256 derived, base64(nonce||ciphertext))
response.status (test scripts only, number) / response.json() / response.text()
set_env/set_header/set_body accept ANY value type — objects/arrays are
auto-JSON-stringified, everything else uses its plain text form. So
`set_header("X-User-Detail", claims.payload)` is valid directly.
CONVENTIONS: env()/header()/cookie() return "" for a missing value — check with
`!= ""`. Field access on an object from json_parse/jwt_decode/response.json()
returns null for a missing field — check that with `!= null`, not `!= ""`.
There's a GLOBAL pre-request/test script pair that runs before every
request's own script and can be overridden by it — mention this only if the
user's request is clearly about something that should apply to every
request, not just one.
Output ONLY the script itself — no explanation, no markdown code fences, no
JS syntax, no pm.* API.
My request: <describe what you want the script to do>