Skip to content

A file format specification for defining HTTP requests, response assertions, and associated data/configuration in "request files".

License

Notifications You must be signed in to change notification settings

testingrequired/reqlang

Repository files navigation

Request Language

A file format specification for defining HTTP requests, response assertions, and configuration in "request files".

Goals

  • 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

Future

  • Typesafe expression language in the templates
  • Chaining requests
  • Response body mapping/transformation/extraction
  • Authenticated requests (e.g. OAuth2) configuration
  • Project workspaces

Request Files

Request files (*.reqlang) are templated markdown files containing an HTTP request, HTTP response assertion, and configuration.

Living Syntax

This is a living syntax subject to change wildly at anytime. The core concepts and goals will remain the same however.

Example

post.reqlang:

```%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}}"
}
```

Request

The request is written as a HTTP request message inside of a %request markdown code block.

```%request
GET https://example.com HTTP/1.1
```

Response

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
```

Matching Rules

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

Configuration

The configuration is TOML written in a %config markdown code block. Its where variables, environments, prompts, and secrets are declared/defined.

Secrets

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"]
Usage
```%config
secrets = ["api_key"]
```

```%request
GET https://example.com HTTP/1.1
x-api-key: {{!api_key}}
```
Goals
  • Secret fetching is outside the scope of the request file
Future
  • Configuring secret fetching in the workspace

Variables & Environments

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"
Default values
[[vars]]
name = "user_id"
default = "12345"

[[vars]]
name = "item_id"

[envs.dev]
item_id = "abcd"

[envs.prod]
user_id = "67890"
item_id = "efgh"
Usage
```%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
```
Warning

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.

Goals
  • Clearly define everything the request and response will need
  • Declare environments once
  • Require variable declaration before definition
Future
  • Value type

Prompts

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
Usage
```%config
[[prompts]]
name = "tags"
```

```%request
GET https://example.com/posts?tags={{?tags}} HTTP/1.1
```

Examples

See all examples for more request files.

Libraries

Rust

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");

Tooling

build-artifacts

These act as both tooling for request file and reference implementations for clients.

CLI

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

Run

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
Examples
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: *
Testing Responses

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"
+  }
 }
-

Parse

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
Examples
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"
  }
}
Filtering

Use tools like jq to extract specific information from the parsed request.

Environment Names

Let a list of environment names defined in the request file.

reqlang parse ./examples/valid/post.reqlang | jq '.envs'
["local", "test", "prod"]
Variables

Let a list of variables provided by the request file.

reqlang parse ./examples/valid/post.reqlang | jq '.vars'
["test_value"]
Prompts

Let a list of prompts required by the request file.

reqlang parse ./examples/valid/post.reqlang | jq '.prompts'
["prompt_value"]
Secrets

Let a list of secrets required by the request file.

reqlang parse ./examples/valid/post.reqlang | jq '.secrets'
["super_secret_value"]
Config Location In Request File

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
}
Request Location In Request File

Get the span of the request in the request file.

reqlang parse ./examples/valid/post.reqlang | jq '.full.request[1]'
{
  "start": 208,
  "end": 388
}
Response Location In Request File

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
Ref Locations In Request File

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
    }
  ]
]
Validation Errors

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"
  }
]

AST

Produce an AST for a request file.

Usage: reqlang ast <path>

Arguments:
  <path>  Path to request file

Options:
  -h, --help  Print help
Examples
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
    }
  ]
]
Filtering
Comments
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
    }
  ]
]

Export

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
Examples
JSON
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": ""
}
HTTP Request Message
reqlang export examples/valid/status_code.reqlang --prompt status_code=201 --format http
GET https://httpbin.org/status/201 HTTP/1.1
Curl command
reqlang export examples/valid/status_code.reqlang --prompt status_code=400 --format curl
curl https://httpbin.org/status/400 --http1.1 -v
Body Text
reqlang export examples/valid/base64decode.reqlang --format body
HTTPBIN is awesome

Validation Errors

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"
  }
]

CLI in Docker

The reqlang CLI can be run from a docker image.

Building

docker build -t reqlang:0.1.0 .

Running

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

VS Code

The VS Code extension acts as an in-editor REST client.

VS Code Extension Screenshot

Desktop Client

The desktop client is a very simple GUI written in egui. It's mostly used for testing.

GUI Client Screenshot

Web Client

The web client is a React app powered by a Rust API.

reqlang-web-client

# Server is running! http://localhost:3000

Port

The port defaults to a random open port but it can be set using the REQLANG_WEB_CLIENT_PORT environment variable.

Contributing

Please see CONTRIBUTING.md for details on how to contribute.

Development Log

You can follow the development in this Bluesky thread.