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

  • Use HTTP request and response messages
  • Easy to read, write, and diff
  • Lives in source control
  • Just a markdown file
  • Templating with variables, prompts, and secret values
  • Environments with environment specific variable values
  • Client/implementation agnostic
  • Statically typed expression language in the templates

Future Goals

  • Chaining requests
  • Response body mapping/transformation/extraction
  • Authenticated requests (e.g. OAuth2) configuration
  • Project workspaces

Request Files

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

# HTTP Request Message

The [request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#http_requests) is defined inside of a markdown code fence block with `%request` as the language.

The request is the only required part of the request file.

```%request
GET https://example.com HTTP/1.1
```
# HTTP Response Message Assertion

The expected HTTP response message assertion is what the actual response will be compared to. It's written inside of a `%response` markdown code block.

The assertion applies the following matching rules:

- Exact match of status code and text
- Exact match of individual headers
- Exact match of the body

```%response
HTTP/1.1 200 OK
```

The request, response, and configuration can be defined in any order.

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

The configuration defines environments, variables, prompts, and secrets accessible during templating. It's written in a `%config` markdown code block.

```%config
# The configuration is written in TOML so comments like this are ignored during parsing.
```

```%request
GET https://example.com HTTP/1.1
```
# Environments & Variables

Variables are declared first then defined in the environment declarations.

Here the variable `var1` is declared then defined in the environments `local` and `prod`.

```%config
[[vars]]
name = "var1"

[envs.local]
var1 = "local value"

[envs.prod]
var1 = "production value"
```

The variable `var1` is then templated using `{{:var1}}`.

```%request
GET https://example.com HTTP/1.1
x-value: {{:var1}}
x-env: {{@env}}
```

The environment used at request file execution time can also be templated using `{{@env}}`.
# Default Variable Values

Variables can also have a default value defined.

```%config
[[vars]]
name = "var1"
default = "default value"

[envs.local]
# No value for `var1` is defined so it will be "default value" in the local environment.

[envs.prod]
var1 = "production value"
```

```%request
GET https://example.com HTTP/1.1
x-value: {{:var1}}
x-env: {{@env}}
```

The environment used at request file execution time can also be templated using `{{@env}}`.
# 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).

```%config
secrets = ["api_key"]
```

```%request
GET https://example.com HTTP/1.1
x-api-key: {{!api_key}}
```
# 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.

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

Contributing

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

Development Log

You can follow the development in this Bluesky thread.

About

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

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •