Redocly CLI Configurable rulesでOpenAPIドキュメントをLintする
この記事は、カオナビ Advent Calendar 2024(シリーズ1) 11日目です。
はじめに
OpenAPIを用いたスキーマ駆動開発では、OpenAPIドキュメントが「Single Source of Truth(唯一の信頼できる情報源)」として重要な役割を担うと思います。
APIの仕様やデータ構造がこのドキュメントに集約されることで、クライアントとサーバーは同じ情報を共有し、整合性を保ちながらの開発がしやすくなるでしょう。
結果として、クライアント・サーバー双方の開発を非同期に進めることも容易になり、開発効率向上が期待できます。
しかし、OpenAPIドキュメントを精度高く一貫性のある状態で維持し、スキーマ駆動開発へ有効活用し続けることは労力が必要でしょう。
このため、OpenAPIドキュメントの記述にはなるべく一貫性や整合性を保つ仕組みがあると良いと思いますが、その実現手段の1つとしてRedocly CLIでLintルールを柔軟に設定できるConfigurable rulesについて焦点を当てたいと思います。
Redocly CLIとは?
Configurable rulesの前にRedocly CLI[1]とは何か触れておきます。
Redocly CLIとは、OpenAPIドキュメントを管理するためのオープンソースツールです。npm
として@redocly/cli
をインストールして利用可能です。
$ npm i @redocly/cli
主に以下のような機能を提供します。
- Lint:OpenAPIドキュメントの静的解析[2]
- Bundle:複数のOpenAPIドキュメントのファイルを1つにまとめる
- ドキュメント生成:ReDocを使って、HTML形式でAPI仕様のドキュメントを生成
Redocly CLIは、これらの機能を活用することによりOpenAPIドキュメントを扱う上で非常に強力なツールとなります。
Lint
上述の通りRedocly CLIにはLint機能があって、JavaScriptに対するESLintのようにOpenAPIドキュメントへのLintが設定できます。
Redoclyによって用意してあるrecommended
のルールセットを利用するところから始めるなら、設定ファイルであるredocly.yml
に以下の記述を入れるだけです。
extends:
- recommended
例えばoperationId
の重複はOpenAPIの仕様上許容されませんが、recommended
に含まれているoperation-operationId-unique
によって検知されます。
openapi.yml
に以下のようなoperationId
の重複があれば
paths:
/cars:
get:
operationId: Car
# ...
post:
operationId: Car
# ...
redocly lint
を実行するとoperation-operationId-unique
ルールのエラーが発生します。
[5] ../openapi/openapi.yaml:27:7 at #/paths/~1cars/post/Car
Every operation must have a unique `operationId`.
25 | # ...
26 | post:
27 | operationId: Car
28 | # ...
29 | /ok:
Error was generated by the operation-operationId-unique rule.
また、ルールセットではなく個別のルール毎に設定したい場合は、redocly.yml
のrules
へルールを設定します。
rules:
operation-operationId-unique: error
Configurable rules
ビルトインのルールだけでもAPIドキュメントを適切に記述するための助けになりますが、ビルトインルールにはない独自のルールを設定したくなることもあるでしょう。
独自のルールをカスタムプラグイン作成して適用する方法もありますが、プラグインを作成せずとも独自のルール設定がConfigurable rulesの仕組みで可能です。
Configurable rulesの設定は、以下のような形でredocly.yml
に設定します。複数ルールでも設定できます。
rules:
rule/my-own-rule-1: ...
rule/my-own-rule-2: ...
# rulesのセクションに他のビルトインルール等と並べて`rule/任意のルール名`のキー名で記載
個々のルールは、以下例に示すようなConfigurable rule objectの形に沿って設定します。
OpenAPI node typesとアサーションの組み合わせで多様なルールを作ることができます。
OpenAPI node typesとは、OpenAPIの仕様を木構造として捉えて、OperationやComponentなどの各オブジェクトをNodeとして表したものになります。
rule/{ユニークなルール名}:
subject: # Lint対象を示す、OpenAPI node typeか any。 必須。
# Subject object
type: PathItem # OpenAPI node type。必須。
property: get # OpenAPI node typeのプロパティー
filterInParentKeys: # アサーション実行対象のsubjectの親キーのリスト
- get
filterOutParentKeys: # アサーション実行対象から除外するsubjectの親キーのリスト
- delete
matchParentKeys: # アサーション実行対象のsabuejctの親キーの正気表現パターンのリスト
- /^p/
assertions: # アサーション。必須。
# Assertion object
required:
- '200'
# ... const, defined, disallowwedなど、さまざまなアサーションが指定可能
where: # subjectの絞り込みを行う
# Where object
subject: # 必須
# Subject object
...
assertions: # 必須
# Assertion object
...
message: "{{assertionName}} failed because the {{subject}} {{property}} didn't meet the assertions: {{problems}}" # アサーション失敗したら表示するメッセージ
suggest: # アサーション失敗解消するための提案リスト
- ...
severity: error # 重大度を示す。error | warn | off。
簡易的なコメントをインラインで記載してますが、大まかにまとめるとsubject
によって対象を指定し、assertions
によってチェックする内容を指定、より細かい対象の絞り込みはwhere
を用いるといった形になります。
構造をざっくり図にしてみると以下のような形でしょうか。
例
具体的にどのようなルールを作れるかイメージを固めるため、実際に例として以下3つOpenAPIドキュメントの記述に制約をかけるルールを作ってみます。
-
operationId
をキャメルケースに -
paths
のパスを$ref
参照でファイル分割するように -
204 No Content
の場合にcontent
を記述しないように
operationId
をキャメルケースに
他のツール(コード生成ツール)でも利用される識別子である等を踏まえてoperationId
は一般的なプログラミングに従って命名するのが望ましいとされますが、仮にキャメルケースで統一するとしたらConfigurable rulesでルール設定することができます。
rule/operationid-case:
subject:
type: Operation
property: operationId
assertions:
casing: camelCase
message: "OperationIdはキャメルケースで命名してください"
severity: error
上述のようにoperationId
を対象とするOpenAPI node typeをsubject
に指定して、assertions
ではcasting
のcamelCase
を指定すると、キャメルケースではないoperationId
が作られないようにLintで弾くことができます。
以下はスネークケースで記述したoperationId
がLintエラーとなった結果です。
[18] ../openapi/openapi.yaml:219:20 at #/paths/~1tickets~1{ticketId}~1qr/get/operationId
OperationIdはキャメルケースで命名してください
218 | entry.
219 | operationId: get_ticket_code
220 | tags:
221 | - Tickets
Error was generated by the rule/operationid-case rule.
キャメルケース以外でもケバブケースやパスカルケースなどがcasing
に値として指定できます。
paths
のパスを$ref
参照でファイル分割するように
OpenAPIドキュメントは$ref
によって別ファイルのコンポーネントを参照して、OpenAPIドキュメントのファイル肥大化を抑制できますが、assertions
にref
を正規表現指定することにより別ファイルへ分割したコンポーネントを$ref
で参照することを強制するルールが作れます。
rule/use-ref-in-paths:
message: "paths/*.ymlにコンポーネントを用意して、$refで参照してください"
severity: error
subject:
type: PathItem
assertions:
ref: /^(\.\/)?paths\/.*\.yml$/
このルール設定があると、以下のように/cars/{carId}
のPathItem
オブジェクトがpaths/
のファイルを参照していない場合、エラーになります。
[1] ../openapi/openapi.yaml:22:3 at #/paths/~1cars~1{carId}
paths/*.ymlにコンポーネントを用意して、$refで参照してください
20 |
21 | paths:
22 | /cars/{carId}:
23 | put:
24 | summary: Update a car
Error was generated by the rule/use-ref-in-paths rule.
PathItem
オブジェクトに限らずコンポーネントとして扱えるOpenAPIドキュメントのオブジェクトは、同様のルール設定によって別ファイルへの分割を強制できそうです。
204 No Content
の場合にcontent
を記述しないように
HTTPステータスが204であれば、content
は返さないようにすることがHTTPの仕様からも望ましいと思いますが、これもカスタムルール設定することで防ぐことができそうです。
rule/no-content-for-status-code-204:
message: "HTTPステータスが204の場合、contentを指定しないでください"
severity: error
subject:
type: Response
filterInParentKeys:
- '204'
assertions:
disallowed:
- content
このようにHTTPステータス204
のResponse
オブジェクトをsubject
に指定してcontent
を許可しないdisallowed
をassertions
としたルール設定すると、204
でcontent
を持つ構造のOpenAPIドキュメントはエラーになります。
[2] ../openapi/openapi.yaml:29:11 at #/paths/~1cars~1{carId}/put/responses/204/content
HTTPステータスが204の場合、contentを指定しないでください
27 | responses:
28 | "204":
29 | content:
30 | application/json:
31 | schema:
Error was generated by the rule/no-content-for-status-code-204 rule.
さいごに
Redocly CLIによってOpenAPIドキュメントへカスタムのLintルールをConfigurable rulesで設定する方法を確認してみました。
OpenAPIドキュメントをただ単にドキュメントとして参照するだけの場合や小規模な開発であれば、このような対応の必要性が薄いかもしれません。
一方で、スキーマ駆動開発においてはOpenAPIドキュメントが非常に重要な役割を担うと思うので、カスタムルールによって得られるものがあるのであれば有効活用していきたいものです。
ただ、yaml
で複雑なルール設定(例えばwhere
を使うような)を行う場合は、インデントで構造を表すyaml
の特性上ルール構造を読み取りづらくなる印象も受けます。そのような場合は、カスタムプラグインを用意してルール適用する方が分かりやすいのかもしれません。
参考
- Getting Started | OpenAPI Documentation
- Redocly/redocly-cli-cookbook: A community-created collection of configuration, plugins and techniques for getting the best from Redocly CLI in every situation.
- Redocly's visual reference to OpenAPI
- RFC: openapi-typescript v7 built on top of Redocly · openapi-ts/openapi-typescript · Discussion #1344
- OpenAPI Map
-
openapi-typescriptを利用している開発者にとっては、v7からバンドルやバリデーションに利用されているものとして認知しているツールかもしれません。 ↩︎
Discussion