Redocly CLIのカスタムLintルールを作ってより強固なOpenAPI仕様を作る
記事中で紹介するカスタムルールの実装はこちらのリポジトリで見ることができます。
Redocly CLI
Redocly CLIは、Redoclyを操作したりOpenAPIドキュメント(以下spec
)をいい感じにしてくれるコマンドを提供してくれます。
幣チームでは、主にドキュメントの検証(Lint)とプレビューを利用しています。
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の設定ファイルにplugins
とrules
を追記します。
※当初、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の検証したいプロパティ(server
やpaths
など)に対応した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ライフを!
-
OAS2に対応した
oas2
も定義できます
https://redocly.com/docs/cli/resources/custom-plugins/#oas-major-versions ↩︎ -
https://github.com/Redocly/redocly-cli/blob/9cad7e2e8de41a12a79c5aafddd3335717698ec0/packages/core/src/visitors.ts#L435 ↩︎
-
type: interger
は、Integer型(format: int32、デフォルト)とLong型(format: int64)が生成されます。
formatを明示的に指定することで「ほんとはLong型なのに、formatをつけなかったせいでInteger型になっちゃった」を防ぎたいです ↩︎
Discussion