# Hen Run API requests as files, from the command line. ```text 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 ```bash cargo install hen ``` ## Usage ``` Usage: hen [OPTIONS] [PATH] [SELECTOR] Arguments: [PATH] [SELECTOR] Options: --export --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: ```bash hen /path/to/collection_directory/collection_file.hen ``` 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: ```bash hen /path/to/collection_directory/collection_file.hen 0 ``` 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: ```bash hen /path/to/collection_directory/collection_file.hen all ``` ### 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. ```bash hen /path/to/collection_directory ``` > 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. ```text [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, ```text * 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, ```text ? 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, ```text $ 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, ```text ~~~ 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, ```text 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, ```text 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. ```text 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. ```bash #!/bin/bash # ./post_request.sh if [ "$STATUS" -eq "200" ]; then echo "✅ [$DESCRIPTION] Received status 200" echo $result else echo "❌ [$DESCRIPTION] Expected status 200 but got $STATUS" echo $result fi ``` ```text 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. ```text 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. ```text * 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. ```text $ 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, ```text $ 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, ```text GET {{ api_origin }}/todos/2 * Authorization = {{ api_key }} ? username = {{ username }} ``` Variables can also be set dynamically by running a shell command. For example, ```text $ api_key = $(./get_secret.sh) $ username = $(echo $USER) ``` Or by setting the variable interactively: ```text $ api_key = [[ api_key ]] ``` ## Additional Syntax ### Comments Comments are lines that are ignored by the parser. Comments start with the `#` character. For example, ```text # 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, ```text # .fragment.hen * Authorization = abc123 ``` ```text 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. ```text $ API_URL = https://lorem-api.com/api --- POST {{ API_URL }}/echo ~~~ application/json { "foo" : "bar" } ~~~ ``` ```bash curl -X POST 'https://lorem-api.com/api/echo' -H 'Content-Type: application/json' -d ' { "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. ```bash hen /path/to/collection_file.hen --benchmark 10 ``` ```text 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 ```text GET https://lorem-api.com/api/user/foo ``` ### Request with Headers, Query Parameters, and Form Data ```text POST https://lorem-api.com/api/echo # Header * foo = abc123 # Query ? bar = abc123 # Form Data ~ baz = abc123 ``` ### Request with Request Body ```text POST https://lorem-api.com/api/jwt ~~~ application/json { "username": "bar", "password": "qux" } ~~~ ``` ### Request with Callback ```bash #!/bin/bash if [ "$STATUS" -eq "200" ]; then echo "✅ [$DESCRIPTION] Received status 200" else echo "❌ [$DESCRIPTION] Expected status 200 but got $STATUS" fi ``` ```text GET https://lorem-api.com/api/user ! sh callback.sh ```