Hen
Run API requests as files, from the command line.
name = Test Collection File
description = A collection of mock requests for testing this syntax.
$ api_key = $(./get_secret.sh)
$ username = $(echo $USER)
$ api_origin = https://lorem-api.com/api
---
# Load other requests.
<< .fragment.hen
---
Some descriptive title for the prompt.
POST {{ api_origin }}/echo/[[ foo ]]
* Authorization = {{ api_key }}
? query_param_1 = value
? username = {{ username }}
~~~ application/json
{ "lorem" : "ipsum" }
~~~
! sh ./callback.sh
Installation
Usage
Usage: hen [OPTIONS] [PATH] [SELECTOR]
Arguments:
[PATH]
[SELECTOR]
Options:
--export
--benchmark <BENCHMARK>
-v, --verbose
-h, --help Print help
-V, --version Print version
Execute a Request
To execute a request, use the hen
command on a file containing a request:
This will prompt you to select a request from the file to execute.
Specifying a Request
You can specify the nth request directly by providing an index as the second argument:
This will bypass the request selection prompt and execute the first request in the file.
Conversely, all requests can be executed with the all
selector:
Selecting a Collection
Alternatively, you can specify a directory of collections. This will prompt you to select a collection file and then a request from that file.
If the directory contains only one collection file, that file will be selected automatically, bypassing the prompt. Dotfiles (files starting with
.
) are ignored by the prompt.
Defining an API Request
An API request is defined in a text file with the .hen
extension.
At a minimum, a request must have a method and a URL. The method is one of GET
, POST
, PUT
, PATCH
, DELETE
, HEAD
, or OPTIONS
. The URL is the endpoint of the request.
[description]
METHOD url
[* header_key = header_value]
[? query_key = query_value]
[~ form_key = form_value]
[~~~ [content_type]
body
~~~]
[! callback]
Headers
Headers are key-value pairs that are sent with the request. Headers are specified with the *
character. For example,
* Authorization = abc123
* Content-Type = application/json
Query Parameters
Query parameters are key-value pairs that are appended to the URL. Query parameters are specified with the ?
character. For example,
? page = 2
? limit = 10
Multipart Form Data
Multipart form data is used to send files or text fields with a request. Multipart form data is specified with the ~
character. For example,
$ file_1 = $(cat ./test_file.txt)
---
POST https://lorem-api.com/api/echo
# form data can be used to send text data
~ form_text_1 = lorem ipsum.
~ form_text_2 = {{ file_1 }}
# form data can also be used to send files
~ file_1 = @./test_file.txt
Request Body
The request body is the data sent with the request. The body is a multiline block specified with the ~~~
characters. The body can optionally be followed by a content type. For example,
~~~ application/json
{
"key": "value"
}
~~~
User Prompts
User input can be requested interactively at runtime by using the [[ variable_name ]]
syntax. A prompt may be used as a value for a query, header, form, or in the request body or URL. For example,
GET https://example.com/todos/[[ todo_id ]]
? page = [[ page ]]
* Origin = [[ origin ]]
~ file = @[[ file_path ]]
Prompts made in a request will be displayed in the terminal when the request is executed.
Callbacks
Callbacks are shell commands that are executed after a request is made. Callbacks are defined in the request definition with the !
character. For example,
GET https://lorem-api.com/api/user
# inline shell command
! echo "Request completed."
# a shell script
! sh ./post_request.sh
If a request has multiple callbacks, they are executed in the order they are defined, top to bottom.
GET https://lorem-api.com/api/user
# This is executed first
! echo '1'
# This is executed second
! echo '2'
Callback Execution Context
Callbacks are executed with response data passed as environment variables. The following environment variables are available to callbacks:
STATUS
: The HTTP status code of the response.RESPONSE
: The response body of the request.DESCRIPTION
: The description of the request.
For example, the following callback will assert that the status code of the response is 200.
#!/bin/bash
# ./post_request.sh
if [; then
else
fi
Echo body w. callback
POST https://lorem-api.com/api/health
! sh ./post_request.sh
Defining an API Collection
A file containing multiple requests is called a collection. Collections can be used to group related requests together.
Collections can start with a preamble that contains metadata about the collection. The preamble is separated from the requests by a line containing three dashes ---
. The same line is also used to separate requests from each other.
name = Optional Collection Name
description = Optional Collection Description
[VARIABLES]
[GLOBAL HEADERS]
[GLOBAL QUERIES]
[GLOBAL CALLBACKS]
---
[request 1]
---
[request 2]
---
etc.
Global Headers, Queries and Callbacks
Any headers, queries or callbacks defined in the collection preamble become global and are included in all requests in the collection.
In the example below, the Authorization
header and page
query is included in all requests in the collection. When each request is executed and a response received, the callback echo "Request completed."
is executed.
* Authorization = foo
? page = 2
! echo "Request completed."
---
GET https://api.example.com/users
---
GET https://api.example.com/posts
Global callbacks are executed before request-specific callbacks.
User Prompts
User prompts can be used in a collection preamble. The user is prompted when the collection is loaded: either directly via the CLI or as a prompt in the interactive mode.
$ foo = [[ bar ]]
---
POST https://lorem-api.com/api/echo
~~~ application/json
{ "foo" : "{{ foo }}" }
~~~
Variables
Variables are key/value pairs defined in the preamble of a collection file with the $
character. For example,
$ api_origin = https://example.com
$ api_key = abc123
$ username = alice
Variables can be used in the request definition by enclosing the variable name in double curly braces. For example,
GET {{ api_origin }}/todos/2
* Authorization = {{ api_key }}
? username = {{ username }}
Variables can also be set dynamically by running a shell command. For example,
$ api_key = $(./get_secret.sh)
$ username = $(echo $USER)
Or by setting the variable interactively:
$ api_key = [[ api_key ]]
Additional Syntax
Comments
Comments are lines that are ignored by the parser. Comments start with the #
character. For example,
# This is a comment
GET https://example.com/todos/2
Fragments
Fragments are reusable blocks of text that can be included in multiple requests. Fragments are defined in a separate file and included in a request with the <<
character. For example,
# .fragment.hen
* Authorization = abc123
GET https://example.com/todos/2
<< .fragment.hen
Fragment paths can be absolute or relative to the collection file.
Fragments can contain multiple requests, headers, query parameters, and request bodies. Fragments can also contain variables and other fragments.
Additional Features
Export Requests
Requests can be exported as curl commands. This is useful for debugging or sharing requests with others.
$ API_URL = https://lorem-api.com/api
---
POST {{ API_URL }}/echo
~~~ application/json
{ "foo" : "bar" }
~~~
Exporting happens once all variables and prompts have been resolved and the request is ready to be executed. Callbacks are ignored during export.
Benchmarking
Requests can be benchmarked by specifying the --benchmark
flag with the number of iterations to run. This will run the request the specified number of times and output the average time taken to complete the request.
Benchmarking request: Echo form data
[##################################################] 100.00%
Mean Duration: 399.95937ms
Variance (%): 0.6831308901940525
Notes:
- Callbacks are ignored when benchmarking.
- User prompts will still be executed in benchmarked requests, and so should be avoided, or used in the preamble only.
Examples
Basic Request
GET https://lorem-api.com/api/user/foo
Request with Headers, Query Parameters, and Form Data
POST https://lorem-api.com/api/echo
# Header
* foo = abc123
# Query
? bar = abc123
# Form Data
~ baz = abc123
Request with Request Body
POST https://lorem-api.com/api/jwt
~~~ application/json
{
"username": "bar",
"password": "qux"
}
~~~
Request with Callback
#!/bin/bash
if [; then
else
fi
GET https://lorem-api.com/api/user
! sh callback.sh