🍏

openapi2aspidaをOpenAPI 3.1.0に雑対応した

2024/10/10に公開

私たちはtobaso(https://www.tobaso.jp)という配信に特化したおたよりサービスを8月末にリリースしました。
そのサービスの開発の中で、バックエンドとのAPI仕様の共有のためOpenAPIのスキーマファイルを作成し、共有することになり、バックエンドで導入したツールの影響でこれまでのOpenAPI 3.0.3が利用できず、3.1.0を利用することになりました。
そこで、これまで利用してきたaspidaのツールチェーンであるopenapi2aspidaが3.0.3までしか利用できなかったため、forkして3.1.0に対応することにしました。

今回は他ライブラリの対応状況とforkしてもaspidaを継続利用することとした理由、OpenAPIの変更の中身と実際に投入した改造内容を紹介していきます。

他ライブラリと共に見た OpenAPI 3.1.0 への対応状況

まず、3.1.03.0.xのマイナーチェンジだとSemantic Versioningとしてみると見えますが、v3.1.0からOpenAPIはSemantic Versioningを放棄しているため互換性はありません。(代わりにJSONSchemaとの互換性は生まれています)

3.0.xに対応しているライブラリで思い浮かぶものというと

あたりでしょうか

この中で3.1.0に対応しているものは

対応しているかよくわからないけれど、動作上対応していそうに見えるものは

  • orval
    • 最近話題に上がることが多い(ような気がする)
    • 対応状況が若干不明
    • 内部的には @apidevtools/swagger-parserに依存していそうだけれど、しっかり読めてないのでよくわからない
    • 3.1.0を入れてもエラーにはならないし、["string", "null"]を string | nullのunionに変換はしてくれるっぽいのでもしかしたら対応しているのかもしれない
      • null系は対応済みでその他若干bugが残っているが、基本3.1に対応もしてるとのこと

対応していないものは

という感じで、まず3.1.0に対応しているライブラリを見つけることが難しいです。

OpenAPI 3.0.3 から 3.1.0への変更点

破壊的変更なのでChangelogを全部見るとさすがに大変なので、公式に出ている Migarating from OpenAPI 3.0 to 3.1.0 を基にピックアップしてみます。

一部Migarating from OpenAPI 3.0 to 3.1.0 からサンプルコードを引用しています

nullが許容される際の記述の変更

typeプロパティが複数の型を定義できるようになるとともに、nullable プロパティが削除(not 非推奨)になり、typenullが追加されました。

3.0.x

type: string
nullable: true

3.1.0

type:
- "string"
- "null"

minimum / maximum プロパティに指定した値を含めるかどうかを指定する方法の変更

minumum / maximum プロパティに指定した値を含めないかどうかを exclusiveMinimum / exclusiveMaximum にて boolean で指定していたが、exclusiveMinimum / exclusiveMaximum に直接値を指定する形に変更になっています。
(このプロパティほとんど使ってないでしょって書かれている...)

3.0.x

minimum: 7
exclusiveMinimum: true

3.1.0

exclusiveMinimum: 7

exampleではなくexamplesを利用するように

単一の例示を行うためにexampleプロパティがありましたが、これがJSON Schemaには存在しないためexamplesに変更になっています

3.0.x

type: string
example: fedora

3.1.0

type: string
examples: 
 - fedora
 - ubuntu

File Upload系の書き方の変更

これまでtype: stringformatプロパティの組み合わせで設定されていたものが、contentEncoding / contentMediaTypeプロパティによって定義されるようになりました

3.0.x

requestBody:
  content:
    multipart/form-data:
  	schema:
       type: object
       properties:
        orderId:
          type: integer
        fileName:
          type: string
          format: binary

3.1.0

requestBody:
  content:
    multipart/form-data:
  	schema:
       type: object
       properties:
        orderId:
          type: integer
        fileName:
          type: string
          contentMediaType: application/octet-stream

他にもJSON Schemaに完全に互換性があるものになったため、追加されたプロパティなどが存在しますが、ここでは3.0.3からの変更分の一部だけをピックアップしました。
constプロパティを使用するなどはあり得そうですが、ここではいったん....)

なぜAspidaを継続利用することにしたのか

元々Aspidaを利用してたというのももちろんですが、APIのPathをメソッドチェーンによって表現する方式が捨てがたいというところが一番大きな理由です。

対応しているライブラリの中で、openapi-typescriptの場合はpathstringから型を取り出す形ですが、若干Intellisenseの効きが悪くクエリパラメータなどが指定された状況でのpathを取得することのできる関数が用意されていないため採用を見送りました。
また、orvalは基本的にOpenAPI内のoperationIdを使ってメゾット名を自動生成しますが、このoperationIdがバックエンドで利用しているツールが自動生成するため、すぐに想像の付くものではなく、わかりやすい共通したルールを整備する時間的猶予があまりなかったため採用を見送っています。

実際に行った改造内容

弊社の環境では上であげた変更点のうち、null関連の記述変更とconstプロパティへの対応のみ影響があったため、それらの対応のみにしています。(typeがstring | numberなどの状態は対応していません。)

null関連の変更は事前にschemaのtypeがArrayかを確認し、nullが含まれる場合はnullbleのフラグを立てるのみ (.diff)

https://github.com/luco-inc/openapi2aspida/blob/62205ac515c5140fe63a500aaf48cc67583c7d83/src/builderUtils/converters.ts#L115-L130

constについても元々あったenumを生成する仕組みを流用し、単一のvalueが存在するenumを生成するように(.diff)

https://github.com/luco-inc/openapi2aspida/blob/62205ac515c5140fe63a500aaf48cc67583c7d83/src/builderUtils/converters.ts#L135-L138

といったかなり大雑把な対応にしています。
あまり大きな変更を入れると今後のopenapi2aspidaの変更に追従するのが大変になりそうということで、openapi-typesのV3.1の型定義に合わせて他にも対応を一部入れていますが、あまり大きな変更はなく、$refの解決方法を若干変えたり、schema.typeがArrayになる可能性が出たのでそのTypeGuardの追加などを行う程度にしています。

最後に

OpenAPI 3.1.0からAPI Clientをつくるライブラリを探したらなかなかなく、3.0.3に戻したという方も少なからず居るのではないでしょうか。
すべてのユースケースにこのようにforkと改造が合うとは思っていませんし、orvalopenapi-typescript、そのほかのライブラリが合う会社も多くあると思いますがこういう選択肢もあるよというご紹介でした。
また、バックエンドで導入したツールについても後日紹介できればと思います!

最後にはなりますが、aspida / openapi2aspida を開発されている solufaさんをはじめとしたContributorの皆様に深く感謝申し上げます。

スコテック

Discussion