🐥

Openapi(v3)からClientを生成するライブラリを作ってみた

2022/06/04に公開

Openapi便利ですよね、そしてOpenapiを利用してコードを生成して、実装を短縮させたり、実装をドキュメントと一致させたいですよね。
既にOpenapiを元にコードを生成するopenapi-generatorというライブラリは存在してまいますが、Openapiのv3.0.0から追加された新機能が追加されていなかったり、バグに所々あたってしまいます。
最初はContributeして対応していこうとopenapi-generatorのコードを周回していましたが、大規模なプロジェクトに一人で立ち向かうのに限界を感じ、openapi-automatonsという新たなOpenapi Generatorを作成しました。

本記事では、openapi-generatorでどのような課題を感じ、どういった問題を解決したライブラリを作成したかについて説明出来ればと思ってます。

openapi-generatorで感じた問題

openapi-generatorは大変便利で、様々な言語やFrameworkにも対応しています。
ただその対応の多さから、一つ一つのGeneratorがメンテナンスされていない事も多く、バグによくあたったり、Openapiのv3.0.0から追加された機能を利用しても対応していなかったりする事が多いです。

バグや対応していない事を回避する方法として、自作Generatorを作成してopenapi-generatorで利用する方法もありますが、Jarファイルを作らなければいけなく、なおかつある程度Generatorを理解しなければ自作Generatorを作成する事が出来ません。

もっと簡単に修正出来たり、最初からOpenapiのv3.0.0に対応していたりすればよいのですが、現状のopenapi-generatorの仕様では容易に行なえません。

どの機能を盛り込んだか

まず作るにあたって自分の要求を言語化しました。

  • Openapiからコードの生成
    • v3.0.0の新機能対応
    • サーバ選択
    • securityの設定を汎用的に行えるようにする(Promise)
    • 任意の出力フォーマットに変更が容易にする
  • Openapiを読み込む
    • $refでの相対パス対応する
      分割したopenapiでも読めるようにする
  • OpenapiのValidation
    • 自動生成前にopenapi.yml自体が正しい形式であるかチェックする
  • 特大ライブラリにならないこと
    • 各々別々のライブラリとして管理し、メンテナンス性を確保する

今回作るにあたって、Openapiのv3.0.0から導入されているParameter Serializationは確実に利用したかったので、必須機能として入れています。
また細かなこれがあったらいいなという、リクエスト時に複数サーバあった場合に選択出来るようにしたりや、Security設定時にPromiseでごちゃごちゃしてしまう部分をシンプルに渡せないか等も考えることも入れました。

また$refも相対パスに対応させ、分割したopenapi.ymlに他のツールを使わずに対応させることも盛り込みました。

逆に捨てたものは、Openapiのv3.1.0から導入されたwebhook機能です。
こちらに関しては現状どんなものを作れば便利なのかわからなかった為、落とす対象となりました。

ライブラリの全体設計

全体設計に関わる課題を踏まえ、私は今回以下のようにライブラリを分割して責務を考えました。

それぞれの責務についてですが、以下のようになっております。

Openapi Automaton
実行者

  • それぞれのライブラリの実行、実行状況の監視
  • automatons.json(Openapi Automaonの設定ファイル)のチェック、読み込み

Validator
openapi.ymlファイルのチェック

Generator
openapi.ymlを元にコードの生成

  • テンプレートを元にファイルに書き出す
  • テンプレートは動的に読む

Parser
openapi.ymlを整形し、中間成果物の作成

  • $refの解決
  • Component名の解決

このようにライブラリを4つに分割しました。
これにより、Openapi Automatonsを利用し、自作のライブラリを作ろうとした時に、Generator部分を新たに作成して読み込ませれば誰でも利用出来る形式になりました。

※Validatorを切り出したのは、openapi-automatonsを利用せず、Validationだけ行いたい方もいるかなと切り出しています。

コード生成方法

では実際の生成方法についてですが、現在node.js経由で利用する方法しかありません。
その為、project rootにてpackage.jsonがある前提となっております。

それではまず、必要ライブラリのダウンロードを行います。

@automatons/typescript-client-axios部分はgeneratorなので、何を生成したいかによって自由に設定してください。

$ yarn add -D openapi-automatons @automatons/typescript-client-axios

ダウンロードが終わりましたら、project root以下のファイルを作成し保存してください。

automatons.json
{
  "openapi": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml",
  "automatons": [{
    "automaton": "@automatons/typescript-client-axios",
    "outDir": "src/client"
  }]
}

automatons.jsonの設定項目ですが、現在はシンプルに以下のようになっています。

property type required description
openapi string true Openapiファイルがある場所
automatons array true それぞれのライブラリの設定
automatons automaton string true ライブラリ名、相対パスでライブラリ場所を指定するのも可能です。
automatons outDir string true Generatorの出力場所

automatons.jsonを作成出来たらGenerateすることが出来ます。
以下のコマンドを入力するとGenerateされます。

$ yarn openapi-automatons

srcディレクトリにコードが生成されていれば完成です。

クライアントの使い方

クライアント名はopenapi-generatorと同様に、指定されたTagに依存します。

クライアントを利用する場合も同様で、以下のような形式で呼ぶことが行なえます。

src/main.ts
import {PetsApi} from "./client";

const petsApi = new PetsApi();

petsApi.listPets({limit: 30});

インターフェイスは以下のようになっております。

  /**
   * listPets
   * @async
   */
  public async listPets(
    queries: { limit?: number },
    headers: {
      "Content-Type": "application/json";
    } = { "Content-Type": "application/json" },
    server: HttpPetstoreSwaggerIoV1Server = { name: "HttpPetstoreSwaggerIoV1" },
    config?: AxiosRequestConfig
  ): Promise<AxiosResponse<Pets>> {
    const path = "/pets";
    const requestConfig = await this.#config.listPets(
      queries,
      headers,
      server,
      config
    );
    return this.axios.get(path, requestConfig);
  }

それぞれのパラメータの説明ですが、Openapiで指定したパラメータが設定出来るようになっています。

queries
get parameterで指定されている、query parameter

headers
headerに指定したい値等

server
Openapiで指定したServer

config
Axiosに設定するConfig

他の入力値を設定するとqueriesの部分が変更されていることが実際生成するとわかると思います。

今回作ってみて思ったこと

今回大きいライブラリを初めて作って思ったことは、テストカバレッジや細かい部分まで手を伸ばすことが本当に難しかったです。
改めて大きいライブラリのメンテナンスや、作成をしている人たちが偉大だと感じられました。
私はこのライブラリを今後もメンテナンスしていく予定ですので、もし使って便利だと感じられたり、バグを見つけた際はissueやpr作成させていただけると助かります。

Discussion