こんにちは。高と申します。
センシンロボティクスでWebやスマホアプリの開発を行っています。
- 今回の記事の1行まとめ
→OpenAPIのYAMLファイルで重複のresponseをaliasでまとめてみた - 前提
→OpenAPI3.0であること(2.0だとおそらく適用できません)
OpenAPI使うと便利ですよね。APIが漏れなく入ることになるから、DOCだけ読めば何があって何がないのかすぐ分かるのが二重丸。
でもAPIを追加したりいじったりする度に、YAMLファイルが長すぎてこれはなんとかならないかと思う毎日。
ファイルはざっとこんな感じ。
paths:
/plants:
get:
summary: List plants
tags:
- plants
responses:
"200":
$ref: "#/components/responses/GetListPlantsResponse"
"400":
$ref: "#/components/responses/ErrorResponse"
"401":
$ref: "#/components/responses/ErrorResponse"
"403":
$ref: "#/components/responses/ErrorResponse"
"404":
$ref: "#/components/responses/ErrorResponse"
"409":
$ref: "#/components/responses/ErrorResponse"
"500":
$ref: "#/components/responses/ErrorResponse"
operationId: list-plants
description: "List all plants"
parameters:
- $ref: "#/components/parameters/xClientId"
- $ref: "#/components/parameters/sortBy"
- $ref: "#/components/parameters/sortDesc"
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/itemsPerPage"
post:
summary: Create a new plant
tags:
- plants
responses:
"201":
$ref: "#/components/responses/CreatePlantResponse"
"400":
$ref: "#/components/responses/ErrorResponse"
"401":
$ref: "#/components/responses/ErrorResponse"
"403":
$ref: "#/components/responses/ErrorResponse"
"404":
$ref: "#/components/responses/ErrorResponse"
"409":
$ref: "#/components/responses/ErrorResponse"
"500":
$ref: "#/components/responses/ErrorResponse"
operationId: create-plant
description: "Create a new plant"
parameters:
- $ref: "#/components/parameters/xClientId"
requestBody:
$ref: "#/components/requestBodies/CreatePlantRequest"
// end pointがすごく並んでいる…
components:
requestBodies:
CreatePlantRequest:
description: Create plant request
required: true
content:
application/json:
schema:
$ref: "./requests/CreatePlantRequest.yaml"
responses:
GetListPlantsResponse:
description: Get list of plants
content:
application/json:
schema:
$ref: "./responses/GetListPlantsResponse.yaml"
CreatePlantResponse:
description: Create plant response
content:
application/json:
schema:
$ref: "./responses/CreatePlantResponse.yaml"
この際components配下は別に良いです。
ここはどうせ流し読みするところではなく検索依存なので。
pathsのところが辛い。
毎回スクロールしていると指が疲れます。
そうだ、なんとか短くしてみよう。
やはり目立つのはresponsesですね。
ロジックを見て使われない40x系を削除すると少しは削減できそうですが…
もっといい考えがある。
繰り返される部分はaliasでまとめるのだ。
とはいえよくわからないのでAIに助けてもらいました。
x-error-responses: &error-responses
"400":
$ref: "#/components/responses/ErrorResponse"
"401":
$ref: "#/components/responses/ErrorResponse"
"403":
$ref: "#/components/responses/ErrorResponse"
"404":
$ref: "#/components/responses/ErrorResponse"
"409":
$ref: "#/components/responses/ErrorResponse"
"500":
$ref: "#/components/responses/ErrorResponse"
paths:
/plants:
get:
summary: List plants
tags:
- plants
responses:
"200":
$ref: "#/components/responses/GetListPlantsResponse"
<<: *error-responses
operationId: list-plants
description: "List all plants"
parameters:
- $ref: "#/components/parameters/xClientId"
- $ref: "#/components/parameters/sortBy"
- $ref: "#/components/parameters/sortDesc"
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/itemsPerPage"
post:
summary: Create a new plant
tags:
- plants
responses:
"201":
$ref: "#/components/responses/CreatePlantResponse"
<<: *error-responses
operationId: create-plant
description: "Create a new plant"
parameters:
- $ref: "#/components/parameters/xClientId"
requestBody:
$ref: "#/components/requestBodies/CreatePlantRequest"
// end pointがすごく並んでいる…
components:
requestBodies:
CreatePlantRequest:
description: Create plant request
required: true
content:
application/json:
schema:
$ref: "./requests/CreatePlantRequest.yaml"
responses:
GetListPlantsResponse:
description: Get list of plants
content:
application/json:
schema:
$ref: "./responses/GetListPlantsResponse.yaml"
CreatePlantResponse:
description: Create plant response
content:
application/json:
schema:
$ref: "./responses/CreatePlantResponse.yaml"
一旦大丈夫そう。
けど問題がありました。
1. CLIがこける
調べてみると使っているCLIの問題でした。
以前はswagger-cliを使ってDOC展開を行ってましたが、
swagger-cliはaliasの解決に対応していないとのこと。
OpenAPI3.0推奨のCLIとして@redocly/cliがあるので、これに入れ替えることで対応。
もちろん引数が変わるので調整が必要です。
さて、なぜswagger-cliがダメなのか。
「まあ古いから対応できてないんだろうな」くらいしか思いませんでしたが
気になる方のためにAIに教えてもらいました。
技術的な理由
1. 古いYAMLパーサーの使用
// swagger-cli内部で使用されているパーサー
const yaml = require('js-yaml'); // 古いバージョン
// 問題のある処理
yaml.safeLoad(yamlContent); // マージキーを完全にサポートしていない
2. マージキー仕様の実装不足
# このようなマージキー構文
x-error-responses: &error-responses
"400":
$ref: "#/components/responses/ErrorResponse"
paths:
/example:
get:
responses:
"200":
description: Success
<<: *error-responses # ← この部分の解釈に問題
3. JSON Schema検証との競合
swagger-cliは以下の順序で処理を行うため:
YAML → JSON変換
JSON Schema検証
参照解決
この過程で<<マージキーが正しく処理されない。
なるほど〜
ちなみに記事の一番上にある「前提」はこの問題から来ています。
<<マージキーの解決のために@redocly/cliを使う必要があるんですが、これがOpen3.0向けのCLIであることから2.0のサポートは制限があるようなんですよね。
2. GUIツールで正常に読み込まれない
私はテキストのまま編集する派なので知らなかったのですが、
社内だとGUIツールが(たぶん)多数派であるため、他にも影響するようであれば問題でした。
でも聞いてみたところ、alias部分のerror responseだけ壊れて個別で書かれている2xx系は表示されるらしいので…ヨシとしました。
表示されない理由は上と同じですね。単に<<を解決できないってだけ。
一旦今回での改善(?)は以上としました。
さらにaliasを使ってparametersをまとめるともっと短くできそうではありますが。
xClientIdとか全体共通のものもそうですし、
paging関連のパラメータも絶対毎回同じなので。
けど上記の問題2番、
つまりGUIツールでparameterが欠落されると流石に分かりにくいだろうというのがありますね。
実はそれよりもっと深刻な問題があって、
そもそもこの形のaliasはobject merge用でありarrayで使うものではないということ。
なのでこの辺は見送りしました。
AIに聞いてみてもこれ以上ピンとくる提案は見つからず。
一旦これで管理してみて、まだ長いと感じるようであればファイル分けを考えることになりそうです。