Auto-Generating a CLI From OpenAPI Specification

As we’ve described many times before, the OpenAPI Specification is a powerful solution for API agility and extensibility. Overseen by the OpenAPI Initiative, the specification drives many modern implementations to new heights. Such is the case with today’s topic, the OpenAPI CLI Generator.

Simply put, it does what it says in the name – this solution generates a CLI from an OpenAPI-specified API. Today, we’re going to cover why one would want to use a CLI, how this solution creates one, and what functions it includes out of the box.

What is a CLI?

A CLI is a Command Line Interface. In essence, this is simply an alternative method of interaction in which a command line (or more specifically, a textual interface) is used rather than the traditional GUI, or Graphical User Interface. A CLI is also often referred to as a console or user interface, and is probably most familiar with users of DOS, Linux, or other operating systems that support command line work (generally speaking, all major operating systems support this). Linux users especially will be familiar with the command shell.

In the API space, a CLI is quite common, but how it’s implemented is very rarely standardized. Each API can have a wide variety of call methodologies and purposes. Since APIs have varying designs, the CLI varies as well. This can cause some difficulty if certain standards aren’t adhered to. While documentation can help quite a bit, designing a CLI takes the same forethought and skill that it takes to design a GUI.

Why would a user want to use a CLI? Why not use a GUI?

Benefits of a CLI

Textual interfaces provide granular control with simple invocations.

There are many reasons a user might prefer a textual interface, rather than a graphical one. First and foremost, a CLI unlocks major power right off the bat, whereas a GUI typically locks this functionality deep in submenus or sub-navigation pages. In a CLI, a user can invoke any function they know, and transform that function in a variety of ways.

In fact, that transformability is a major part of why a CLI is so powerful, and often so desired amongst the technically minded. Because a user can invoke anything they are familiar with, they are able to manipulate and transform these invocations into something greater than the initial request. They often can do this more effectively, with fewer steps, and in a clearer way than they would otherwise be able to if they were using a GUI. This delivers incredible granularity to the end-user, which is refreshing in an age of limited, albeit flashy, web and GUI interfaces.

Additionally, since the user is free to invoke and transform, the CLI can be used as a tool to leverage scripting and other automation solutions. This reduces the effort behind repeated actions upon a resource and opens the user up to a much wider world of regular, repeated interactivity that does not require their direct presence or effort. For API providers, offering a CLI means improving the end developer experience.

One of the biggest benefits of a CLI is the fact that they offer piping and combinatory actions. Function outputs and even functions themselves can be piped as input into other functions, combining data sources, processes, and executions into a singular, granularly controlled, and highly comprehensive function and data flow. This can occur before, during, or after execution, delivering incredible power and value for simple invocations.

Drawbacks of a CLI

All of that said, CLI as a concept has two major drawbacks – discoverability and intuitiveness. A GUI is designed to be intuitive, natively understandable, and parseable by the end-user without too much work. Its learning curve is meant to be shallow and gradual, and everything that the user can do – albeit severely limited compared to the CLI – is ready and apparent.

CLIs, on the other hand, have a steeper learning curve and are generally seen as less user-friendly. Though there’s an incredible amount of power under the hood, it takes more training and skill to actively utilize it. In fact, many CLI users are simply using the base functions the CLI offers without ever knowing that there are many other functions below the surface – and in those cases, using the CLI makes no sense.

Many developers try and skirt this problem by providing ample help files or strong documentation, but ultimately, this is often asking quite a lot from a user just trying to make some simple invocations. Whereas in a GUI the question of “how do I do this?” might be answered with a simple visual scan, CLIs can be denser.

While CLIs are insanely powerful, the reality is that many new users will look at a CLI, immediately think of DOS, and start looking for the GUI. This is not something that is really solvable outside of education, but it is a consideration to look at, especially if your CLI is going to be facing specifically novice users (such as an API meant to facilitate mocking/testing or demonstrate specific data flow types).

As such, CLIs are best served to power users and those who need them, not as a general-purpose solution for everyone.

OpenAPI CLI Generator

In order to leverage the benefits of the CLI, the OpenAPI CLI Generator was created. This open-source project’s purpose is to generate CLIs from an OpenAPI 3 specification fileset and then deliver that CLI effectively to the end-user.

OpenAPI CLI Generator takes a YAML based OpenAPI file and converts it into something your Terminal could interact with.

CLIs created by OpenAPI CLI Generator have the following core features:

  • Waiters: Waiters are declaratively defined special commands and parameters that allow a command to be blocked for a short time. The command will be forced to literally “wait” for a certain condition, and when that condition is met, the command will be allowed to complete its function. This is most useful for asynchronous operations, or operations that must be invoked now, but which depend on as-of-yet uncreated content.
  • Authentication: Authentication methods including Auth0 are supported for the securing of the API. Authentication is fundamentally an access control mechanism, and given the power of the CLI, it makes perfect sense to have a robust and restrictive system for authentication.
  • Caching: The OpenAPI CLI Generator packages efficient and fast caching as a core component. This is extremely important and can be a huge data saving measure when the CLI is presented with high volume requests.
  • Configuration: Configuration is handled through Viper, with support for JSON, YAML, or TOML. This allows a greater amount of CLI granularity and makes OpenAPI CLI Generator more flexible for a wide range of purposes.
  • Filtering and Projecting: Response filtering and projecting is handled by JMESPath, which allows for greater control over the output of the CLI content itself.
  • Output coloring: Chroma allows for better user experience by coloring and improving the output of the CLI. This might seem minor, but it’s quite valuable, and every improvement on user experience when it comes to CLIs is important.
  • Functional input/output: The CLI allows for standard in (stdin) or CLI shorthand. This enables a greater amount of access on the CLI to functionality and allows for efficient piping and control of request flow.
  • Logging: Logging is handled by zerolog to provide faster logging of the CLI. This is often a blocker for adopters of this kind of technology, so removing that obstacle is key.
  • Extensibility: HTTP Middleware is supported through Gentleman, which opens the CLI up to a great amount of extensibility.

Now that we’ve seen what this solution is, let’s look at how to actually implement it. Much of this is borrowed from the official documentation, but it bears contextualizing and framing within a theoretical project.

Clone or Download the OpenAPI CLI Generator project here on Github.

Using OpenAPI CLI Generator

Before we do anything, we need to install Go. For many developers, this is already the state of the stack. Next, the project will be grabbed and brought into the current stack using the following command.

$ go get -u github.com/danielgtaylor/openapi-cli-generator

Now that we’ve brought this active, we need to start building the initial platform. First, we need to make the project directory, and then generate the commands file:

# Set up your new project
$ mkdir my-cli && cd my-cli

$ openapi-cli-generator init 

$ openapi-cli-generator generate openapi.yaml

Next, after adding this line to your main.go file:

openapiRegister(false)

We can initiate the building of the client itself.

$ go install

From here, you can do any number of things. One of the most useful things you can do in terms of user experience is to create some aliases. Aliases allow complex calls to be invoked using replacement content. Many people familiar with CLIs in Linux, Windows, etc. might be familiar with this. It’s the solution that allows you type cat instead of concatenate and still invoke the function as you would normally.

In order to set the alias, you need to both set the operation being changed, and the alias that can be invoked. For example:

paths:
  /orders:
    get:
      operationId: ReadOrder
      x-cli-aliases:
      - ro

This allows you to use -ro to invoke “ReadOrder” in the orders path. While this particular command is rather short, once you start adding parameters and doing these invocations over and over, the shorthand alias quickly becomes extremely valuable.

With that set, we can take a brief detour and consider other sets of functions related to orders. We may not what the CLI to have documentation and invocations for everything our API can do – a lot of our order information may be private. In this case, we can exclude paths, operations, and/or parameters using the paths modifiers:

paths:
  /included:
    description: I will get included in the CLI.
  /excluded:
    x-cli-ignore: true
    description: I will not

Now that we have our properly defined paths and our alias, we need to look at the actual function of the invocation. In our theoretical case, our order system is delayed by a few seconds, as there is processing validation that occurs on a separate server. Accordingly, we need to add a waiter to the function so that we can ensure the order that is expected is actually served in the proper initial call.

In this example, we’re going to create a waiter that will delay the response by 30 seconds for each request. If the wrong employee_id value is entered, (i.e., entering an employee ID that does not exist), the invocation will fail and return a functional error code.

x-cli-waiters:
  order-charged:
    delay: 30
    attempts: 10
    operationId: ReadOrder
    matchers:
    - select: response.body#status
      expected: charged
    - select: response.status
      expected: 404
      state: failure
    after:
      CreateOrder:
        id: response.body#employee_id

By the end of this section, you will have created a CLI that outputs all current orders filed under an employee ID. More importantly, you can invoke this functionality using either ReadOrder, or its alias -ro. Additionally, this request has been pushed into a waiter system, which allows for a delay on the invocation to allow our systems to properly catch up and serve the actual data requested.

Conclusion

In short, CLIs are exceptionally powerful, but they are often difficult to create, and sometimes even more difficult to use. This particular OpenAPI CLI generator has been designed to fix that, offering an intuitive system for creation. Paired with a powerful feature set, it brings greater extensibility, functionality, and usability. It’s a quite powerful solution, offering a rather easy method for creation despite the amount of granularity and control it grants.

What do you think? Are CLIs as effective as GUIs? Would you use this to adopt a CLI on your API? Let us know below!