openapiの管理をtypespecに移行する
tags: openapi
openapiを利用しているプロジェクトでstoplight studioを試して挫折した結果yamlを手で変更して定義を管理していたが、APIが増えてきてyamlの管理が面倒になってきたので、typespecに移行することにした。
対象の読者
現在openapiを利用しているプロジェクトでopenapiの定義をtypespecに移行したい人。
注意
一応typespecには OpenAPI3 to TypeSpec | TypeSpec というマイグレーションのためのコマンドが用意されているが、自分たちは書き方を学んだり、ファイルを分割したいために手動で移行することにした。(github copilotは利用するけど)
また利用しなかった理由としてstoplight studioを中途半端に利用していたからかidの部分がエラーになったというのもある。
プロジェクト設定
Installation | TypeSpecを参考にプロジェクトをセットアップすると、プロジェクトのpackage.jsonが上書きされてしまうので注意が必要。
今回は既存のプロジェクトに導入する形で進めるが、ディレクトリを切ってmonorepoのように管理する方が楽な気がする。
typespecのインストール
npm i -D @typespec/compiler@latest @typespec/http@latest @typespec/rest@latest @typespec/openapi@latest @typespec/openapi3@latest
これでコンパイラやプラグイン等がインストールされるがtsp initを実行して生成されるpackage.jsonではバージョン番号ではなくlatestが指定されているので同じになるように修正する
変更後一応再度パッケージをインストールしておく
npm i
typespecの設定
tspconfig.yamlを生成する
emit:
  - '@typespec/openapi3'
options:
  '@typespec/openapi3':
    emitter-output-dir: '{output-dir}/schema'
    openapi-versions:
      - 3.1.0
もし出力するファイル名を変更したい場合は以下のようにoutput-fileを指定する
emit:
  - '@typespec/openapi3'
options:
  '@typespec/openapi3':
    emitter-output-dir: '{output-dir}/schema'
    output-file: 'sample-openapi.yaml'
    openapi-versions:
      - 3.1.0
tsp compile
実際にopenapiを生成できることを確認する
適当なtypespecファイルを作成しておく。以下のファイルはtsp initを実行した際に生成されるtypespecファイルをそのまま利用している。
import "@typespec/http";
using Http;
@service(#{ title: "Widget Service" })
namespace DemoService;
model Widget {
  id: string;
  weight: int32;
  color: "red" | "blue";
}
model WidgetList {
  items: Widget[];
}
@error
model Error {
  code: int32;
  message: string;
}
model AnalyzeResult {
  id: string;
  analysis: string;
}
@route("/widgets")
@tag("Widgets")
interface Widgets {
  /** List widgets */
  @get list(): WidgetList | Error;
  /** Read widgets */
  @get read(@path id: string): Widget | Error;
  /** Create a widget */
  @post create(@body body: Widget): Widget | Error;
  /** Update a widget */
  @patch update(@path id: string, @body body: Widget): Widget | Error;
  /** Delete a widget */
  @delete delete(@path id: string): void | Error;
  /** Analyze a widget */
  @route("{id}/analyze") @post analyze(@path id: string): AnalyzeResult | Error;
}
コンパイルを実行
npx tsp compile ./main.tsp
コンパイルが成功するとtsp-output/schemaディレクトリにopenapi.yamlが生成される。
既存のopenapiからのマイグレーション作業
既存のopenapiはそれなりに大きいので一気にtypespecに移行はできない。そのため今回は以下のようにする
- typespecでAPI定義を書く
 - typespecでopenapiを生成する
 - openapi-merge-cli - npm を利用して生成したopenapiと既存のopenapi定義とマージする
 - マージしたopenapiを利用してコード生成を行う
 
npx tsp compile ./main.tsp
npx openapi-merge-cli -i ./tsp-output/schema/openapi.yaml -i ./openapi.yaml -o ./merged-openapi.yaml
# プロジェクトのopenapigenerateを実行(sample)
npm run openapi-generate
今回初めてopenapi-merge-cliを利用したが、結構ちゃんとマージしてくれるので助かった。
まとめ
生成手順を踏む必要がある間は辛いが、完全に移行すればtypespecで生成し、openapi-generateを実行するだけになるので楽になるはず。
またtypespecはvscode拡張を入れると補完が効いたり、typescriptのように型を定義して使い回せるので、yamlを手で書く辛さを完全に消し去ってくれて比較的書く気になれるのでおすすめです。