Design notes

Pipeline

.http text --parser--> ParsedFile --scope/order--> dependency graph --runner--> Results --report--> output
  1. Parse (parser.py): split on ###, then per block read file variables, directives, request line, headers, body, and ?? assertions. Every error carries the 1-based line number of the offending line. Assertion syntax is validated here too (assertions.parse), so a typo never reaches the network.
  2. Order (scope.py): scan every templated string of every request (URL, headers, body, parts, assertions) for {{name.response...}} references. Plain variables are followed transitively, so @auth = Bearer {{login.response.body.$.token}} makes every request that uses {{auth}} depend on login. # @after adds explicit edges.
  3. Select (runner.select): --only picks requests by name, title or tag with fnmatch globs, then adds the transitive dependencies so a selected request can always resolve its references.
  4. Run (runner.py): resolve templates, build the body (build.py), send with httpx, evaluate assertions, store the response in the scope so later requests can reference it.
  5. Report: terminal rendering, saved responses, JUnit, JSON, Markdown.

Variable resolution

Lookup order: --var first, then file variables, then the selected environment. Values may themselves contain templates and are resolved recursively with a stack, so @a = {{b}} / @b = {{a}} raises variable cycle: a -> b -> a instead of recursing forever.

Worked example:

@baseUrl = http://h
@auth = Bearer {{login.response.body.$.token}}

### login
POST {{baseUrl}}/login
### me
GET {{baseUrl}}/me
Authorization: {{auth}}

Dependencies: me -> {login} (through auth). Execution order is login, me, even if me were written first in the file.

Ordering and cycles

The graph is checked with a depth-first search that tracks the current path; the first back edge reports the cycle, for example dependency cycle: a -> b -> a. A request referencing itself is a one-node cycle. References to unknown names are reported with the file and line of the referencing request.

The topological order is stable: requests keep file order except where a dependency must move earlier. Sequential runs (-j 1, the default) execute in that order.

Concurrency (-j N)

A request is submitted to a thread pool once none of its dependencies is still pending or running. Independent requests overlap; chains stay sequential. Requests with no declared relationship may run in any order, so state-changing sequences that rely on file order (delete, then check it is gone) need # @after. The cookie jar is shared by the whole run, so cookies set by one request are visible to requests that start after it.

If a request fails or is skipped its dependents are still attempted; they fail with a precise message (request 'login' has no response) rather than being silently dropped.

Secret masking

Masker hides values in anything that is printed or saved: the values from the private env file and every {{$env.X}} value (replaced by ****), header names that look sensitive (auth, token, secret, password, api-key, cookie), and JSON keys containing password, secret, token or api_key. The real values are only used for the actual request.

Retries, timeouts, redirects

--retries N retries transport errors and 502/503/504 with exponential backoff (0.2 s, 0.4 s, ...). A # @timeout directive overrides --timeout for one request. Redirects are followed unless --no-redirect or # @no-redirect. trust_env is off for httpx so proxy and CA environment variables do not change behaviour silently; use --proxy and --insecure explicitly.

curl and HAR

restbook curl resolves a request in dry mode (response references stay as {{...}} placeholders, since nothing was sent) and prints one command. Multipart requests use -F, file bodies --data-binary @file, and -L is emitted unless the request has # @no-redirect. import-curl parses a command with shlex and supports -X -H -d/--data* -F -u -A -b -e -L -I -m --url; -d without a content type gets curl's default (form-encoded) or JSON when the data looks like JSON. import-har converts each HAR entry, dropping HTTP/2 pseudo headers, Host and Content-Length. The exported curl command re-imports to the same method, URL, headers and body (covered by round-trip tests).

Exit codes

0 all requests passed (skipped requests do not count as failures), 1 at least one failed assertion or request error, 2 usage, parse, environment or dependency errors (nothing was sent).