In this post we'll explore Restish, a CLI for APIs with built-in OpenAPI support. How does it go from an OpenAPI service description to CLI commands & arguments? Read on to find out!
Autodiscovery
Restish supports OpenAPI autodiscovery using several different mechanisms. You can provide an RFC 8631 service-desc
link relation, an RFC 5988 describedby
link relation, or provide an /openapi.yaml
or /openapi.json
with your API.
Link Relation Header
When using the service-desc
or describedby
link relation, Restish follows the link to find the OpenAPI. For example, if you get https://api.example.com/
it might return the following header:
Link: </path/to/openapi.yaml>; rel="service-desc"
Restish would then fetch https://api.example.com/path/to/openapi.yaml
and load that into operations, arguments, flags, etc.
Fallback Mechanism
If not link relation header is present, a fallback mechanism is used. Restish looks for https://api.example.com/openapi.yaml
or https://api.example.com/openapi.json
. If found, it will load it.
Anatomy of a CLI Operation
A CLI operation can consist of several parts:
Name | Description |
API | Short-name of the API, configured when registering the API with Restish (can be anything you want). |
Operation | OpenAPI Operation ID |
Options | Optional operation query or header parameter(s) |
Arguments | Required operation path parameter(s) |
Request Body | Optional request body, which can be passed in via stdin or via CLI Shorthand in the command. |
Aside from those, there is also the act of generating --help
output: markdown descriptions and output JSON Schemas which need to be handled.
This is how those would map into an OpenAPI 3 YAML:
Github API Example
Let's take a look at a real-world example from the Github V3 OpenAPI. Here is a truncated version of the code search operation:
"/search/code":
get:
summary: Search code
operationId: search/code
parameters:
- name: q
description: The query contains one or more search keywords...
in: query
required: true
schema:
type: string
- name: sort
description: Sorts the results of your query...
in: query
required: false
schema:
type: string
enum:
- indexed
- "$ref": "#/components/parameters/order"
- "$ref": "#/components/parameters/per-page"
- "$ref": "#/components/parameters/page"
responses:
'200':
description: Response
content:
application/json:
schema:
type: object
required:
- total_count
- incomplete_results
- items
properties:
total_count:
type: integer
incomplete_results:
type: boolean
items:
type: array
items:
"$ref": "#/components/schemas/code-search-result-item"
examples:
default:
"$ref": "#/components/examples/code-search-result-item-paginated"
'304':
"$ref": "#/components/responses/not_modified"
'503':
"$ref": "#/components/responses/service_unavailable"
'422':
"$ref": "#/components/responses/validation_failed"
'403':
"$ref": "#/components/responses/forbidden"
This translates into the following command help in Restish showing you how to use it. Note the operation ID search-code
and the parameters like --sort
and --per-page
from the $ref
s above. The response is also used to generate a terminal-friendly representation of the response schema so users know what to expect as output.
$ restish github search-code --help
Description truncated for example...
## Response 200 (application/json)
`schema
{
incomplete_results: (boolean)
items: [
{
git_url: (string)
html_url: (string)
name: (string)
path: (string)
repository: {
archive_url: (string)
assignees_url: (string)
blobs_url: (string)
branches_url: (string)
collaborators_url: (string)
comments_url: (string)
commits_url: (string)
compare_url: (string)
contents_url: (string)
contributors_url: (string)
description: (string)
downloads_url: (string)
events_url: (string)
fork: (boolean)
forks_url: (string)
full_name: (string)
git_commits_url: (string)
git_refs_url: (string)
git_tags_url: (string)
hooks_url: (string)
html_url: (string)
id: (number)
issue_comment_url: (string)
issue_events_url: (string)
issues_url: (string)
keys_url: (string)
labels_url: (string)
languages_url: (string)
merges_url: (string)
milestones_url: (string)
name: (string)
node_id: (string)
notifications_url: (string)
owner: {
avatar_url: (string)
events_url: (string)
followers_url: (string)
following_url: (string)
gists_url: (string)
gravatar_id: (string)
html_url: (string)
id: (number)
login: (string)
node_id: (string)
organizations_url: (string)
received_events_url: (string)
repos_url: (string)
site_admin: (boolean)
starred_url: (string)
subscriptions_url: (string)
type: (string)
url: (string)
}
private: (boolean)
pulls_url: (string)
stargazers_url: (string)
statuses_url: (string)
subscribers_url: (string)
subscription_url: (string)
tags_url: (string)
teams_url: (string)
trees_url: (string)
url: (string)
}
score: (number)
sha: (string)
url: (string)
}
]
total_count: (number)
}
`
Usage:
restish github search-code [flags]
Flags:
--accept application/vnd.github.v3+json Setting to...
-h, --help help for search-code
--order desc Determines whether the first...
--page int Page number of the results to fetch. (default 1)
--per-page int Results per page (max 100) (default 30)
--q string The query contains one or more search...
--sort indexed Sorts the results of your query...
Global Flags:
...
Note also that all parameters use double slashes (--
), since single slashes are reserved for Restish use. Conversely, all Restish parameters are prefixed with --rsh-
in order to prevent collisions.
For operations with required arguments and/or bodies, Restish is able to generate usage and example documentation as well, including example CLI Shorthand input. For example, when creating a new repo fork:
## Request Schema (application/json)
`schema
{
organization: (string) Optional parameter to specify the organization name if forking into an organization.
}
`
...
Usage:
restish github repos-create-fork owner repo [flags]
Examples:
restish repos-create-fork owner repo organization: string
restish repos-create-fork owner repo <input.json
Overrides
Sometimes you might want the CLI operation name or parameter name to be different from what the official name is in the API, or hide a particular deprecated parameter, or even tell Restish how to automatically configure auth. These are all possible with Restish OpenAPI Extensions.
Name | Description |
x-cli-aliases | Sets up command aliases for operations. |
x-cli-config | Automatic CLI configuration settings. |
x-cli-description | Provide an alternate description for the CLI. |
x-cli-ignore | Ignore this path, operation, or parameter. |
x-cli-hidden | Hide this path, or operation. |
x-cli-name | Provide an alternate name for the CLI. |
For example, in the search operation above, the query parameter is named q
and would show up in Restish as --q
which is not very friendly. You might rename it via x-cli-name: query
so that people can use --query
instead.
Conclusion
Hopefully this has shed some light on how Restish is able to dynamically generate CLI commands from OpenAPI specifications, and how you can expect those commands to operate if you are already familiar with the backend API.