🚀

Redocly CLIのカスタムLintルールを作ってより強固なOpenAPI仕様を作る

2022/12/12に公開

https://adventar.org/calendars/8218


記事中で紹介するカスタムルールの実装はこちらのリポジトリで見ることができます。
https://github.com/mahaker/redocly-custom-lint-rule-demo

Redocly CLI

Redocly CLIは、Redoclyを操作したりOpenAPIドキュメント(以下spec)をいい感じにしてくれるコマンドを提供してくれます。
幣チームでは、主にドキュメントの検証(Lint)とプレビューを利用しています。
https://redocly.com/docs/cli/

type:numberを禁止したい

開発しているアプリケーションでは、specからJavaのコードを生成しています。
type: numberで定義したプロパティはJavaのBigDecimal型に変換されるのですが、BigDecimal型のリクエストやレスポンスを使うことはなさそうでした。
実際にコード生成せずに確認したい、CIで検出したい、というのもあり独自のLintルールを実装することにしました。

カスタムルールを実装する

Redocly CLIではプラグインの仕組みがあり、そこでカスタムルールを実装できます。
カスタムルール自体はJavaScriptで実装しました。

ルール本体はこんな実装になります。
Visitor(後述)を返す関数を定義し、exportsします。

// plugins/rule/no-type-number.js

const report = (newLocation) => ({
  message: 'type: number is not allowed',
  suggest: ['type: integer', 'format: int32 or int64'],
  location: newLocation,
})

function NoTypeNumber() {
  // 必要であれば、何かをカウントするような変数を定義することもできます
  return {
    // リクエストパラメータやクエリの1要素に対応したVisitor
    Parameter: {
      skip({ schema }) {
        return !schema || !schema.type || schema.type !== 'number'
      },
      enter(parameter, ctx) {
	// ctx.report()を呼び出すことで「Lintに引っかかった」ことを通知します
        // パラメータ名をreportします
        ctx.report(report(ctx.location.child(['name']).key()))
      },
    },
    // レスポンスのプロパティに対応したVisitor
    SchemaProperties(properties, ctx) {
      Object.keys(properties).forEach(k => {
        if (properties[k].type === 'number')
          // プロパティ名をreportします
          ctx.report(report(ctx.location.child([k]).key()))
      })
    }
  }
}

module.exports = NoTypeNumber

また、プラグイン本体はこんな実装になります。
プラグインIDや、プラグインに含まれるLintルールなどをexportsします。
今回はOAS3に対応したLintルールを実装したので、rules.oas3に先ほどのルールをマッピングします。[1]
ルール本体が小さかったりする場合は、このファイルに直接ルールを書くのも良いでしょう。

// plugins/custom-lint-rules.js

const NoTypeNumber = require('./rules/no-type-number')

module.exports = {
  id: 'my-local-plugin', // 'my-local-plugin'はプラグインIDと呼ばれます
  rules: {
    oas3: {
      'no-type-number': NoTypeNumber, // 'no-type-number'はルールIDと呼ばれます
    },
  },
}

CLIの設定

最後に、実装したプラグインをCLIに認識させます。
Redocly CLIの設定ファイルにpluginsrulesを追記します。
※当初、rulesを追記しておらずハマりました。

# redocly.yaml

apis:
  sample@v1: 
    root: openapi/openapi.yaml
extends:
  - recommended
plugins:
  - './plugins/custom-lint-rules.js' # プラグインへのパスを設定
rules:
  no-unused-components: error
  my-local-plugin/no-type-number: error # プラグインID/ルールIDをキーに、エラーレベルを設定

これで設定も完了しました。
lintコマンドを叩いてみると、type: numberとしている箇所がLintに引っかかっているのが分かります。
いいですね!

Visitorオブジェクト

ここで、実装について少し紹介します。
カスタムルールではVisitorと呼ばれるオブジェクトを定義します。
Visitorとspecは1対1で対応しており、specの検証したいプロパティ(serverpathsなど)に対応したVisitorを定義することで、そのプロパティを検証することができます。
※NGだった場合は第二引数のcontextオブジェクトに生えているreportメソッドを呼び出すだけです。
定義可能なVisitor一覧はドキュメント実装を参照してください。

Visitorは「enter/leave/skipのプロパティを持ったオブジェクト」もしくは「関数」として定義できます。
なお、Visitorを関数として定義した場合は、enterプロパティだけのオブジェクトと同義になります。[2]
つまり下記は同じ定義となります。

SchemaProperties: {
  enter(properties, ctx) {
    // do something
  }
}

SchemaProperties(properties, ctx) {
  // do something
}

まとめ

Redocly CLI、そしてそのカスタムルールの作り方を紹介しました。
幣チームでは「type: integerかつformatがついていないプロパティ」を検出するカスタムルールも実装しています。[3]
ビルトインのLintルールの実装も非常に参考になるので、ぜひ見てみてください。

みなさんもより良いOpen APIライフを!

脚注
  1. OAS2に対応したoas2も定義できます
    https://redocly.com/docs/cli/resources/custom-plugins/#oas-major-versions ↩︎

  2. https://github.com/Redocly/redocly-cli/blob/9cad7e2e8de41a12a79c5aafddd3335717698ec0/packages/core/src/visitors.ts#L435 ↩︎

  3. type: intergerは、Integer型(format: int32、デフォルト)とLong型(format: int64)が生成されます。
    formatを明示的に指定することで「ほんとはLong型なのに、formatをつけなかったせいでInteger型になっちゃった」を防ぎたいです ↩︎

Discussion