AQ Tech Blog

OpenAPIをLinterとFormatterで管理するまで

作成者: tomoaki.matsuura|2023年12月15日

 

本記事はAsiaQuest Advent Calendarの13日目です。

はじめに

こんにちは、DE部GE課の松浦です。
前回はOpenAPIの移行についての話でしたが、今回は LinterとFormatterの話です。
このあたりの管理は面倒なので、やっぱり機械に任せたいですよね。

OpenAPIのLinter

Linterとはコードを分析して、プロジェクト内で決めた記述方法やルールと違っている部分を検出するツールのことです。
JavaScript とかだと、ESLint が有名ですね。
OpenAPIではフォーマットがバージョンごとに決まっているため、それに従っていない場合にエラーとして判定してくれます。
また、問題はありませんが非推奨みたいな書き方はwarningとして表示してくれたりします。
このあたりはツールによって表示の仕方が違っていたりします。

どのツールにするかですが、以下の基準で選定しました。

  1. CLIとして実行できる
  2. エラー出力がわかりやすい
  3. ドキュメントが充実している
  4. リポジトリの更新が活発
  5. カスタマイズができる
  6. 導入が簡単
    • npmをメインに利用しているので、npmベースが望ましい
  7. ある程度普及している

上記の条件で自分が調べた限りでは、以下2つが良いのかなと思いました。
自分のチームではSpectralの方を採用しましたが、どちらも良いツールだと思います。
使い方も似ているので、ドキュメントや出力、ルールセットを確認してみて、よりチームにあったものを選ぶのが良いと思います。
RedoclyとSpectralでは、デフォルトだと出力結果が違っていることがあるので、その点には注意してください。

本記事では上記2ツールについての比較しますが、他にも同様の機能を有していると思われるopticdev/optic、Go製のdaveshanley/vacuum、シンプルなNode製CLIツールのsuperfaceai/openapi-linter、LinterツールをまとめてCI上で実行できるsuper-linter/super-linter(super-linterで動く実態はSpectral)等があるので、興味があればチェックしてみてください。

Spectral

Stoplight社が開発しているツールになります。
GUI上でOpenAPIが書けるStoplight Studioも開発してたりします。

Spectralはnpm経由でインストールでき、実行も以下のコマンドで終わるので、非常に簡単です。

spectral lint target_file.yaml

複数ファイルに対して実行したい場合や、YAMLやJSONに適応したい場合でも、glob syntaxが使えるので、以下のように書けばOKです。

spectral lint /your/openapi/file/**/*.{json,yml,yaml}

チェック項目については、組み込みの設定を読み込む仕組みが提供されています。
.spectral.yamlファイル内にextends: "spectral:oas"と定義するだけで、OpenAPIのバージョンに応じた設定を読み込んでくれます。

ルール詳細はこちらのOpenAPI Rulesに掲載されています。
未使用Component等、推奨事項ではない項目についてはデフォルトだとwarningとして出力されますが、Change Rule Severityに掲載されている上書き方法で、自身のプロジェクトにあったルールへ変更できます。

実行時の出力はこんな感じです。一行で出力される感じが見やすくて良いですね。
行数もちゃんと出てます。

/your/yaml/path/petstore.yaml
2:6 warning info-contact Info object must have "contact" object. info
2:6 warning info-description Info "description" must be present and non-empty string. info
11:9 warning operation-description Operation "description" must be present and non-empty string. paths./pets.get
15:11 warning operation-tag-defined Operation tags must be defined in global tags. paths./pets.get.tags[0]
41:10 warning operation-description Operation "description" must be present and non-empty string. paths./pets.post
45:11 warning operation-tag-defined Operation tags must be defined in global tags. paths./pets.post.tags[0]
56:9 warning operation-description Operation "description" must be present and non-empty string. paths./pets/{petId}.get
56:9 error path-params Operation must define parameter "{petId}" as expected by path "/pets/{petId}". paths./pets/{petId}.get
60:11 warning operation-tag-defined Operation tags must be defined in global tags. paths./pets/{petId}.get.tags[0]
62:11 error oas3-schema "0" property must match exactly one schema in oneOf. paths./pets/{petId}.get.parameters[0]
110:17 error oas3-schema "type" property must be equal to one of the allowed values: "array", "boolean", "integer", "number", "object", "string". Did you mean "array"?. components.schemas.Error.properties.message.type

✖ 11 problems (3 errors, 8 warnings, 0 infos, 0 hints)

OAIが出してるOpenAPIのexampleを改変して読ませました。

Redocly CLI

Redoc社が開発しているツールになります。
OpenAPI仕様書をイケてるHTMLドキュメントに変換するRedocがあったりします。
Redocに関しては以前ブログでも紹介したことがあるので興味があれば、そちらもチェックしてもらえると嬉しいです。

Redocly CLIもnpm経由でインストールでき、実行もSpectralと同様にとても簡単です。glob syntaxも使えます。

redocly lint target_file.yaml

チェック項目についても、Spectral同様に組み込みルールがRedocly CLI側で用意されています。
組み込みルールを利用する場合はSpectralと違って、とくにファイルは必要ありません。

Ruleはredocly.yamlを定義することでオーバーライドできます(Configファイルの設定案内)。
ルール詳細については、こちらのRulesに掲載されているので、Spectral同様にカスタマイズ可能です。

実行結果は、Redoclyの方が全体のどこにあるかはわかりやすいですが、行数が多いです。
デフォルトのconfigを利用しているアナウンスが最初にでるので、親切です。
Redoclyだと、10項目のうち6項目がエラーで4項目がwarningとして出力されているので、デフォルトの内容に違いがあります。

No configurations were provided -- using built in recommended configuration by default.

validating petstore.yaml...
[1] petstore.yaml:110:17 at #/components/schemas/Error/properties/message/type

`type` can be one of the following only: "object", "array", "string", "number", "integer", "boolean", "null".

Did you mean: array ?

108 | format: int32
109 | message:
110 | type: arrayg
111 |

Error was generated by the spec rule.


[2] petstore.yaml:62:11 at #/paths/~1pets~1{petId}/get/parameters/0

The field `in` must be present on this level.

60 | - pets
61 | parameters:
62 | - name: petId
63 | required: true
… | < 2 more lines >
66 | type: string
67 | responses:
68 | '200':

Error was generated by the spec rule.


[3] petstore.yaml:61:7 at #/paths/~1pets~1{petId}/get/parameters

The operation does not define the path parameter `{petId}` expected by path `/pets/{petId}`.

59 | tags:
60 | - pets
61 | parameters:
62 | - name: petId
63 | required: true

Error was generated by the path-parameters-defined rule.

----------
長いので省略
----------

[10] petstore.yaml:67:7 at #/paths/~1pets~1{petId}/get/responses

Operation must have at least one `4XX` response.

65 | schema:
66 | type: string
67 | responses:
68 | '200':
69 | description: Expected response to a valid request

Warning was generated by the operation-4xx-response rule.


petstore.yaml: validated in 19ms

❌ Validation failed with 6 errors and 4 warnings.
run `redocly lint --generate-ignore-file` to add all problems to the ignore file.

Formatter

Formatter とはコードを整形してくれるツールのことです。
YAMLであれば、文字列のクォートや、配列の書き方を統一してくれます。

YAMLのFormatterについては、Linterほど選択肢がありませんでした。

以上の3つを候補としてあげましたが、Prettierを採用しました。
少なくとも自分は知らなかったのですが、2018-07-29 から Prettier で YAML のフォーマットができるようになっていたようです
Prettierを選択した理由としては、以下の3つが主な要因です。

  • npm管理できること
  • すでにTypeScriptで利用実績があったこと
  • 自分のチームではクォーテーションの問題が主に取り上げられていたので、クォーテーションの統一ができればよかったこと

どのようにして自動実行させたか

huskyを利用しました。
huskyはgit commit前に任意の処理を簡単に挟み込めるようにするツールです。
LinterやFormatterで採用したツールと同様にnpm経由でインストールできます。

huskyを利用して、package.jsonにLintとFormatの処理をまとめたコマンドをキックさせるようにしました。
デメリットとしては、コミット処理が若干重くなります。

別の方法としては、GitHub Actions等のCIツール上でLint、Formatをキックして自動コミットや、フォーマット違反によるエラー検知を実施する方法もあります。
今回は対象のファイルが少なかったことや、コミット前に実施したかったことがあったので、huskyを利用しました。

まとめ

OpenAPIのバージョン移行に引き続き、今回ではLinterとFormatterの導入しました。
導入の結果、統一されていなかったフォーマット問題の解決や、ルール違反の事前検知ができるようになりました。

こういった作業を人力で行うのはつらいので、OpenAPIを書いている方は導入をオススメします!
機械に任せられるところは、機械に任せて楽に仕事しましょう。