/openapi-client

A program with libraries needed to generate D client code corresponding to an OpenAPI specification.

Primary LanguageD

OpenAPI Client

The purpose of this tool is to generate client libraries that are compatible with the REST API of a service, whose interface is described via an OpenAPI/Swagger specification document.

The format of OpenAPI specifications can be found here: https://swagger.io/specification/

Initially, this library was developed in order to support the generation of a client for the Stripe API, however, it is intended to be a general tool usable for all OpenAPI specifications.

Many notable companies, such as Amazon, Google, Microsoft, Slack, and many more publish public OpenAPI specifications that can be used by this library to generate D clients. A more comprehensive list of public OpenAPI specifications can be found at APIs.guru.

Current Features

  1. An executable that, when given an OpenAPI3 JSON specification file, writes a client in D.
  2. Generation of D classes under the "model" subpackage representing OpenAPI3 components/schemas.
  3. Generation of D classes under the "service" subpackage representing OpenAPI3 paths.
    1. Generation of inline inner static classs for request parameters in HTTP headers, the Query String, or Path templates.
    2. Generation of inline inner static classes for response bodies.
    3. Generation of a "ResponseHandler" class permitting callers to receive typed responses per HTTP status code defined in the OpenAPI3 specification.
  4. HTTP requests and responses in application/json format.
  5. HTTP requests in application/x-www-form-urlencoded format, encoded in "deepObject" format.
  6. A traditional interative interface and a "builder API" interface for clearer coding.

Compilation

This project is written in the D programming language using the standard build tool dub.

The binary executable can be built using the command:

dub build

The executable will be located in target/openapi-client.

Command-Line Usage

The program openapi-client takes as input an OpenAPI specification in JSON format and generates D code that can be used as part of a project.

The following command-line options are supported:

  • --targetDir = The base directory in which files should be generated, e.g. the "source" directory.
  • --packageRoot = The root package in which to generate files, e.g. "myapi.modules".
  • --openApiSpec = The OpenAPI specification to read in order to generate a client, e.g. the Stripe OpenAPI specification.

Alternatively, the executable can be run directly using dub, e.g.:

dub run openapi-client -- --targetDir=source --openApiSpec=json/spec3.json --packageRoot=stripe

Project Usage

Typically the openapi-client program is invoked to generate source for a project. However, because the OpenAPI specification does not frequently change, it is helpful to only regenerate this source conditionally.

For example, the following snippet can be added to your D project's dub.sdl file to only generate the client source code when the dub build --config=generate command is run:

# To re-create the Stripe API source files (e.g. when the OpenAPI spec changes, run the
# command: dub build --config=generate
configuration "generate" {
  preGenerateCommands "dub run openapi-client -- --targetDir=source --openApiSpec=json/spec3.json --packageRoot=stripe"
}

Generated Client Usage

The following code snippet makes use of the client library generated from the Stripe OpenAPI specification using the traditional iterative approach:

import stripe.security : Security;
import stripe.service.v1_charges_service : V1ChargesService;
import vibe.data.json : serializeToJsonString;

// Stripe's OpenAPI specification has two valid security schemes:
//   - HTTP Basic Auth (named "BasicAuth")
//   - HTTP Bearer Auth (named "BearerAuth")
Security.configureBasicAuth(
    "sk_test_51MFbD...vri",  // Username / API key
    "");                     // With Stripe, the password is always blank.

// Service classes are created from valid URL paths + "Service", e.g. "/v1/charges" => "V1ChargesService".
auto service = new V1ChargesService();

// Each endpoint has a "Params" object which covers any path, query-string, header, or cookie parameters.
auto params = new V1ChargesService.GetChargesParams();
params.customer = "Bob";
params.starting_after = "abcde";

// Some requests have a request body, which will be an argument to the method, e.g. "postCharges".

// Different HTTP status codes can be associated with different data types.
// Create a handler object and add your own delegates that say what to do with each response.
auto handler = new V1ChargesService.GetChargesResponseHandler();
// This handler is for a successful 200 response, there's also a default handler for errors.
handler.handleResponse200 = (V1ChargesService.GetChargesResponseHandler.ChargeList chargeList) {
  // Simply print out our response in JSON format.
  writeln(serializeToJsonString(chargeList));
};

// Now call the desired endpoint and your handler will be invoked depending on the response.
service.getCharges(params, handler);

If you prefer a "builder API" style of programming, then the following code is functionally identical:

import stripe.security : Security;
import stripe.service.v1_charges_service : V1ChargesService;
import vibe.data.json : serializeToJsonString;

auto service = new V1ChargesService();
service.getCharges(
    V1ChargesService.GetChargesParams().builder()
        .customer("Bob")
        .starting_after("abcde")
        .build(),
    V1ChargesService.GetChargesResponseHandler.builder()
        .handleResponse200((V1ChargesService.GetChargesResponseHandler.ChargeList chargeList) {
          writeln(serializeToJsonString(chargeList));
        })
        .build());

If you are familiar with the HTTP interface of the API you intend to use, you will find that the client matches it very closely, and that all the generated data types, classes, and parameters are documented from within the generated code.

Future Features

  1. Improved unit test coverage.
  2. Support additional request content-types, such as multipart/form-data and text/plain.
  3. For HTTP requests in application/x-www-form-urlencoded format, support additional encoding styles such as: pipeDelimited, spaceDelimited, simple, form, label, and matrix.
  4. Support additional response body content-types, such as application/pdf and text/plain.
  5. Generation of client libraries in other programming langugages, e.g. C, Java, Rust, Go, etc.
  6. Alternative network/JSON library generation, rather than just Vibe.d.
  7. Generation of union types to reflect OpenAPI types using "anyOf" validation.