A file format specification for defining HTTP requests, response assertions, and configuration in "request files".
- Can be treated as a markdown file
- HTTP request and response messages
- Easy to read, write, and diff
- Lives in source control
- Templating with variables, prompts, and secret values
- Environments with environment specific variable values
- Client/implementation agnostic
- Typesafe expression language in the templates
- Chaining requests
- Response body mapping/transformation/extraction
- Authenticated requests (e.g. OAuth2) configuration
- Project workspaces
Request files (*.reqlang
) are templated markdown files containing an HTTP request, HTTP response assertion, and configuration.
This is a living syntax subject to change wildly at anytime. The core concepts and goals will remain the same however.
```%config
secrets = ["super_secret_value"]
[[prompts]]
name = "prompt_value"
[[vars]]
name = "test_value"
[envs.test]
test_value = "test_value"
[envs.prod]
test_value = "prod_value"
[envs.local]
test_value = "local_value"
```
```%request
POST https://httpbin.org/post HTTP/1.1
{
"env": "{{@env}}",
"value": "{{:test_value}}",
"prompted_value": "{{?prompt_value}}",
"secret_value": "{{!super_secret_value}}"
}
```
The request is written as a HTTP request message inside of a %request
markdown code block.
```%request
GET https://example.com HTTP/1.1
```
The response assertion is the (optional) expected HTTP response message the actual response will be compared to. It's written inside of a %response
markdown code block.
```%request
GET https://example.com HTTP/1.1
```
```%response
HTTP/1.1 200 OK
```
Client implementations can choose how to match the response against the expected response. Here are a list of recommended ways to match.
- Exact match
status code
- Exact match
status text
- Exact match
header value
of headers present in the expected response - Exact match
body
The configuration is TOML written in a %config
markdown code block. Its where variables, environments, prompts, and secrets are declared/defined.
Secrets are protected values referenced by a name and declares what secrets will be required. How secret values are fetched is up to client implementations. They can be referenced using the {{!secret_name}}
syntax.
Secrets are optional but if they are declared, they must be at the top of the config block (due to how TOML parses tables).
secrets = ["api_key"]
```%config
secrets = ["api_key"]
```
```%request
GET https://example.com HTTP/1.1
x-api-key: {{!api_key}}
```
- Secret fetching is outside the scope of the request file
- Configuring secret fetching in the workspace
Variables contain environmental variables that can be used in the request or response. A list of variable names is first declared.
Variables can be templated using the {{:var_name}}
syntax. The environment of the request execution can be referenced using the {{@env}}
syntax.
[[vars]]
name = "user_id"
[[vars]]
name = "item_id"
Then enviroments are declared with the appropriate values.
[[vars]]
name = "user_id"
[[vars]]
name = "item_id"
[envs.dev]
user_id = 12345
item_id = "abcd"
[envs.prod]
user_id = 67890
item_id = "efgh"
[[vars]]
name = "user_id"
default = "12345"
[[vars]]
name = "item_id"
[envs.dev]
item_id = "abcd"
[envs.prod]
user_id = "67890"
item_id = "efgh"
```%config
[[vars]]
name = "user_id"
[[vars]]
name = "item_id"
[envs.dev]
user_id = 12345
item_id = "abcd"
[envs.prod]
user_id = 67890
item_id = "efgh"
```
```%request
GET https://{{@env}}.example.com/users/{{:user_id}}/items/{{:item_id}} HTTP/1.1
```
Be sure to declare env definition blocks using the [env.ENV]
syntax in TOML. You can use env.ENV.name = value
but they must be at the top of the config block. This is due ot how TOML handles parsing tables.
- Clearly define everything the request and response will need
- Declare environments once
- Require variable declaration before definition
- Value type
Prompts are values provided by the user at request execution time. These are "inputs" to the request file. They can be templated in the request and responses using the {{?prompt_name}}
syntax.
[[prompts]]
name = "tags"
description = "Tags included as a query param" # Optional
default = "tag1,tag2" # Optional
```%config
[[prompts]]
name = "tags"
```
```%request
GET https://example.com/posts?tags={{?tags}} HTTP/1.1
```
See all examples for more request files.
The reqlang crate is a library working with request files.
use reqlang::prelude::*;
let request_file_text = fs::read_to_string("./path/to/requestfile.reqlang")
.expect("Should have been able to read the file");
const ast = Ast::from(&request_file_text);
const parsed_request_file = parse(&ast).expect("should be a valid request file");
These act as both tooling for request file and reference implementations for clients.
The reqlang
CLI validates and exports requests in to a variety of formats (http
, curl
, json
).
reqlang
Command to work with request files
Usage: reqlang [COMMAND]
Commands:
export Export request to specified format
ast Produce an AST for a request file
parse Parse a request file
run Run a request file
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
Execute the request from a request file.
Usage: reqlang run [OPTIONS] <path>
Arguments:
<path> Path to request file
Options:
-e, --env <env> Resolve with an environment
-P, --prompt <prompts> Input a prompt value
-S, --secret <secrets> Input a secret value
-f, --format <format> Format the response [default: http] [possible values: http, json, body]
-t, --test Test if the response matches the expected response, if defined
-h, --help Print help
reqlang run ./examples/valid/status_code.reqlang --prompt status_code=200
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
connection: keep-alive
content-length: 0
server: gunicorn/19.9.0
access-control-allow-credentials: true
access-control-allow-origin: *
Run the response assertion, if defined in the request file, the response will be compared to the expected response.
reqlang run examples/valid/mismatch_response.reqlang --test
See: mismatch_response.reqlang
HTTP/1.1 200 OK
connection: keep-alive
server: gunicorn/19.9.0
access-control-allow-origin: *
access-control-allow-credentials: true
date: Sun, 02 Feb 2025 03:55:33 GMT
content-type: application/json
content-length: 429
{
"slideshow": {
"author": "Yours Truly",
"date": "date of publication",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
],
"title": "Sample Slide Show"
}
}
Response assertion failed:
-HTTP/1.1 201 Created
+HTTP/1.1 200 OK
-x-test-value: ...
{
"slideshow": {
- "author": "Yours Truly",
+ "author": "Yours Truly",
+ "date": "date of publication",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
],
- "title": "Test Slide Show"
- },
- "extra": true
+ "title": "Sample Slide Show"
+ }
}
-
Validate and parse request files. It returns a JSON object with info about the request file: environment names, variables, prompts, secrets, the (untemplated) request itself.
Usage: reqlang parse <path>
Arguments:
<path> Path to request file
Options:
-h, --help Print help
reqlang parse ./examples/valid/status_code.reqlang
{
"vars": ["test_value"],
"envs": ["prod", "test", "local"],
"prompts": ["prompt_value"],
"secrets": ["super_secret_value"],
"request": {
"verb": "POST",
"target": "https://httpbin.org/post",
"http_version": "1.1",
"headers": [],
"body": "{\n \"env\": \"{{@env}}\",\n \"value\": \"{{:test_value}}\",\n \"prompted_value\": \"{{?prompt_value}}\",\n \"secret_value\": \"{{!super_secret_value}}\"\n}\n\n"
}
}
Use tools like jq
to extract specific information from the parsed request.
Let a list of environment names defined in the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.envs'
["local", "test", "prod"]
Let a list of variables provided by the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.vars'
["test_value"]
Let a list of prompts required by the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.prompts'
["prompt_value"]
Let a list of secrets required by the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.secrets'
["super_secret_value"]
Get the span of the config, if defined, in the request file. Otherwise it's null
.
reqlang parse ./examples/valid/post.reqlang | jq '.full.config[1]'
{
"start": 0,
"end": 204
}
Get the span of the request in the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.full.request[1]'
{
"start": 208,
"end": 388
}
Get the span of the response, if defined, in the request file. Otherwise it's null
.
reqlang parse ./examples/valid/post.reqlang | jq '.full.response[1]'
null
Get the span of all the template references (variables, prompts, secrets, providers), if defined, in the request file.
reqlang parse ./examples/valid/post.reqlang | jq '.full.refs'
[
[
{
"Provider": "env"
},
{
"start": 208,
"end": 388
}
],
[
{
"Variable": "test_value"
},
{
"start": 208,
"end": 388
}
],
[
{
"Prompt": "prompt_value"
},
{
"start": 208,
"end": 388
}
],
[
{
"Secret": "super_secret_value"
},
{
"start": 208,
"end": 388
}
]
]
If the request file is invalid, a list of errors will be returned instead.
reqlang parse examples/invalid/empty.reqlang
[
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"severity": 1,
"message": "ParseError: Request file is an empty file"
}
]
Produce an AST for a request file.
Usage: reqlang ast <path>
Arguments:
<path> Path to request file
Options:
-h, --help Print help
reqlang ast examples/valid/as_markdown.reqlang
[
[
{
"Comment": "# Request Files Are Markdown Files\n\nAnything outside of the config, request, or response code blocks is treated as markdown. This lets you document your request files in a way that is easy to read and understand.\n\n## Config\n\nPrompt the user for the `status_code` to return.\n\n"
},
{
"start": 0,
"end": 275
}
],
[
{
"ConfigBlock": [
"[prompts]\n# Status code the response will return\nstatus_code = \"\"",
{
"start": 286,
"end": 352
}
]
},
{
"start": 275,
"end": 355
}
],
[
{
"Comment": "\n\n## Request\n\nThis will respond with the prompted `status_code`.\n\n"
},
{
"start": 355,
"end": 421
}
],
[
{
"RequestBlock": [
"GET https://httpbin.org/status/{{?status_code}} HTTP/1.1",
{
"start": 433,
"end": 490
}
]
},
{
"start": 421,
"end": 493
}
]
]
reqlang ast examples/valid/as_markdown.reqlang | jq 'map(select(.[0] | has("Comment")))'
[
[
{
"Comment": "# Request Files Are Markdown Files\n\nAnything outside of the config, request, or response code blocks is treated as markdown. This lets you document your request files in a way that is easy to read and understand.\n\n## Config\n\nPrompt the user for the `status_code` to return.\n\n"
},
{
"start": 0,
"end": 275
}
],
[
{
"Comment": "\n\n## Request\n\nThis will respond with the prompted `status_code`.\n\n"
},
{
"start": 355,
"end": 421
}
]
]
Parse and template the request file then export it in different formats.
Usage: reqlang export [OPTIONS] <path>
Arguments:
<path> Path to request file
Options:
-e, --env <env> Resolve with an environment
-P, --prompt <prompts> Pass prompt values to resolve with
-S, --secret <secrets> Pass secret values to resolve with
-f, --format <format> Format to export [default: json] [possible values: http, curl, json, body]
-h, --help Print help
reqlang export examples/valid/status_code.reqlang --prompt status_code=200 --format json
# This is the same thing
reqlang export examples/valid/status_code.reqlang --prompt status_code=200
{
"verb": "GET",
"target": "https://httpbin.org/status/200",
"http_version": "1.1",
"headers": [],
"body": ""
}
reqlang export examples/valid/status_code.reqlang --prompt status_code=201 --format http
GET https://httpbin.org/status/201 HTTP/1.1
reqlang export examples/valid/status_code.reqlang --prompt status_code=400 --format curl
curl https://httpbin.org/status/400 --http1.1 -v
reqlang export examples/valid/base64decode.reqlang --format body
HTTPBIN is awesome
If the request file is invalid or there were errors templating, a list of errors will be returned instead.
reqlang export examples/invalid/empty.reqlang
[
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"severity": 1,
"message": "ParseError: Request file is an empty file"
}
]
The reqlang
CLI can be run from a docker image.
docker build -t reqlang:0.1.0 .
A directory of request files can be mounted inside the container's /usr/local/src
directory to make them accessible.
docker run --rm --read-only \
-v "/$PWD/examples":/usr/local/src/examples:ro \
reqlang:0.1.0 \
export \
./examples/valid/delay.reqlang \
-f curl \
-P seconds=5 | bash
# HTTP/1.1 201 CREATED
# Date: Sat, 14 Dec 2024 19:20:26 GMT
# Content-Type: text/html; charset=utf-8
# Content-Length: 0
# Connection: keep-alive
# Server: gunicorn/19.9.0
# Access-Control-Allow-Origin: *
# Access-Control-Allow-Credentials: true
The VS Code extension acts as an in-editor REST client.
The desktop client is a very simple GUI written in egui. It's mostly used for testing.
The web client is a React app powered by a Rust API.
reqlang-web-client
# Server is running! http://localhost:3000
The port defaults to a random open port but it can be set using the REQLANG_WEB_CLIENT_PORT
environment variable.
Please see CONTRIBUTING.md for details on how to contribute.
You can follow the development in this Bluesky thread.