Rustman Scripting
Docs · Scripting

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 scriptTest script
RunsBefore the request is sentAfter the response is received
env(...)Active environment's variables
header(...)The request's own headersThe response's headers
cookie(...)The request's cookie jar
responseNot available — doesn't exist yetAvailable — status + body
body(), url()The request's own body/URLSame — what was actually sent (not the response)
headers()All of the request's own headersAll of the response's headers
Effects that matterset_header, set_body, set_envtest, 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.

If a script errors, nothing partial happens: the request still runs (or the response still displays), the error is shown instead of any effects, and none of that run's earlier 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

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

#OperatorsNotes
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)

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

FunctionSignatureNotes
env(name)(string) → stringActive environment variable. "" if unset, not null.
set_env(name, value)(string, any) → nullWrites 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) → stringCase-insensitive. Request headers pre-request, response headers in a test script. "" if absent.
headers()() → objectEvery 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) → nullAdds/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) → stringCase-insensitive lookup on the request's cookie jar. "" if absent.
body()() → stringThe 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) → nullReplaces the outgoing request body (pre-request scripts only). Same auto-conversion as set_env/set_header.
url()() → stringThe request's own URL — available in both script slots.
test(name, condition)(string, bool) → nullRecords a pass/fail on the response's Tests tab.
print(value)(any) → nullLogs value (auto-converted like set_env) to the Tests tab's console section — for debugging without an assertion.
base64_encode(s)(string) → stringStandard base64 (not URL-safe).
base64_decode(s)(string) → stringAccepts standard or URL-safe-no-pad base64. Null if invalid or not UTF-8 once decoded.
jwt_decode(token)(string) → objectNo signature verification — claims only. Accepts a bare token or one prefixed with "Bearer ". Returns { header, payload }, or null if malformed.
json_parse(s)(string) → valueParses arbitrary JSON text. Null on invalid JSON.
json_stringify(value)(value) → stringSerializes any value back into real, properly-quoted JSON — the inverse of json_parse/jwt_decode's output.
aes_encrypt(text, key)(string, string) → stringAES-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) → stringReverses 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>