🎄

Redocly CLI Configurable rulesでOpenAPIドキュメントをLintする

2024/12/11に公開

この記事は、カオナビ Advent Calendar 2024(シリーズ1) 11日目です。

https://qiita.com/advent-calendar/2024/kaonavi

はじめに

OpenAPIを用いたスキーマ駆動開発では、OpenAPIドキュメントが「Single Source of Truth(唯一の信頼できる情報源)」として重要な役割を担うと思います。
APIの仕様やデータ構造がこのドキュメントに集約されることで、クライアントとサーバーは同じ情報を共有し、整合性を保ちながらの開発がしやすくなるでしょう。
結果として、クライアント・サーバー双方の開発を非同期に進めることも容易になり、開発効率向上が期待できます。

しかし、OpenAPIドキュメントを精度高く一貫性のある状態で維持し、スキーマ駆動開発へ有効活用し続けることは労力が必要でしょう。
このため、OpenAPIドキュメントの記述にはなるべく一貫性や整合性を保つ仕組みがあると良いと思いますが、その実現手段の1つとしてRedocly CLIでLintルールを柔軟に設定できるConfigurable rulesについて焦点を当てたいと思います。

Redocly CLIとは?

Configurable rulesの前にRedocly CLI[1]とは何か触れておきます。

https://github.com/Redocly/redocly-cli

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に以下の記述を入れるだけです。

redocly.yml
extends:
  - recommended

例えばoperationIdの重複はOpenAPIの仕様上許容されませんが、recommendedに含まれているoperation-operationId-uniqueによって検知されます。

openapi.ymlに以下のようなoperationIdの重複があれば

openapi.yml
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.ymlrulesへルールを設定します。

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を用いるといった形になります。
構造をざっくり図にしてみると以下のような形でしょうか。
Configurable rulesの構成

具体的にどのようなルールを作れるかイメージを固めるため、実際に例として以下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ではcastingcamelCaseを指定すると、キャメルケースではない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に値として指定できます。

https://redocly.com/docs/cli/rules/configurable-rules#casing-example

pathsのパスを$ref参照でファイル分割するように

OpenAPIドキュメントは$refによって別ファイルのコンポーネントを参照して、OpenAPIドキュメントのファイル肥大化を抑制できますが、assertionsrefを正規表現指定することにより別ファイルへ分割したコンポーネントを$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ステータス204Responseオブジェクトをsubjectに指定してcontentを許可しないdisallowedassertionsとしたルール設定すると、204contentを持つ構造の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の特性上ルール構造を読み取りづらくなる印象も受けます。そのような場合は、カスタムプラグインを用意してルール適用する方が分かりやすいのかもしれません。

参考

脚注
  1. openapi-typescriptを利用している開発者にとっては、v7からバンドルやバリデーションに利用されているものとして認知しているツールかもしれません。 ↩︎

  2. OpenAPIドキュメントのLint機能を持つ類似ツールとしてはSpectralが挙げられます。 ↩︎

GitHubで編集を提案

Discussion