OpenAPI のレスポンス定義では additionalProperties: false したほうが良い
Leaner 開発チームの黒曜(@kokuyouwind)です。
Leaner の技術スタック紹介(2021年7月版) で、新規プロダクトでは OpenAPI を用いスキーマ検証する話に触れました。
今回はこれに関するちょっとした Tips です。
TL;DR
Committee::Rails を使うときはレスポンス定義で additionalProperties: false
を指定しておくと、定義以外の属性でテストが引っかかるので安全でした。
Committee:Rails を利用したスキーマ検証
rspec-request_describer と Committee:Rails を導入すると、以下のように Request Spec を書くだけでレスポンススキーマの検証が行えます。
# spec/support/match_schema_definition_shared_examples.rb
RSpec.shared_examples 'match schema definition' do |status: 200|
it "スキーマ定義の status:#{status} に合致したレスポンスを返す" do
subject
expect(response.status).to eq(status)
assert_response_schema_confirm(status)
end
end
# spec/requests/users_spec.rb
RSpec.describe 'Users', type: :request do
describe 'GET /user' do
context 'ログイン済みのとき' do
include_context 'logged in as a user'
it_behaves_like 'match schema definition'
end
context '未ログインのとき' do
it_behaves_like 'match schema definition', status: 401
end
end
end
この際、単純にスキーマを記述するだけでは、レスポンスに余分な属性が含まれても検証に通ってしまいます。
// schema.json
// GET /user のレスポンス定義
"properties": {
"id": {
"type": "integer",
"minimum": 1
},
"email": {
"type": "string",
"format": "email"
},
"name": {
"type": "string"
},
"required": [
"id",
"email"
]
},
class UsersController < ApplicationController
def show
render json: user.as_json # created_at, updated_at なども含まれている
end
end
$ bundle exec rspec spec/requests/users_spec.rb
Finished in 2.45 seconds (files took 0.56858 seconds to load)
# テストが通ってしまう!
created_at
などであれば大した問題ではありませんが、例えば他ユーザの本名や IP アドレスが意図せず API レスポンスに含まれてしまうと大問題になりえます。
こうした挙動を防ぐには、レスポンス定義に additionalProperties: false
を指定します。
"properties": {
// ...
},
"additionalProperties": false, // <-- 追加
すると、 spec 実行時に余分な属性値を検出しテストが失敗するようになります。
$ bundle exec rspec spec/requests/users_spec.rb
Failures:
1) Users POST /user/image ログイン済みのとき behaves like match schema definition スキーマ定義の status:200 に合致したレスポンスを返す
Failure/Error: assert_response_schema_confirm(status)
Committee::InvalidResponse:
#/components/schemas/User does not define properties: createdAt, updatedAt
なお components
を使って定義したモデルを参照している場合は、モデル側で additionalProperties
を設定する必要があります。
"User": {
"type": "object",
"additionalProperties": false,
"properties": {
// ...
どうやってこの設定に辿り着いたか
Committee の Issue を response extra
や response unknown
などで検索して、以下の Issue にたどり着きました。[1]
こちらはレスポンスではなくリクエストパラメタについての話でしたが、回答で触れられている additionalProperties: false
がレスポンス定義でも使えるか試したところうまく動いた、という流れです。
調べ始めた段階では Committee
側の設定の問題だと思っていたので、 OpenAPI 定義の話にたどり着けたのは幸運でした。それと同時に、やはり大体同じ疑問を持ってる人がいるので Issue を漁るのは大事ですね。
参考文献
Committee:Rails を用いたスキーマ検証については GiFT 様のcommittee×OpenAPI×RailsでスキーマファーストなAPI開発を参考にさせていただいています。
こちらの記事に書かれた以下の記述が気になり、方法がないか調べた結果 additionalProperties
の設定を見つけることができました。
また、仕様には記載していない created_at や updated_at がレスポンスに含まれていてもテストは通ってしまいます。
他に日本語で additionalProperties
について触れた記事がないか探した結果、以下の記事を見つけました。
こちらでは allOf
を用いた場合などについても触れられています。
宣伝
Leaner Technologies では Committee でスキーマ駆動な API 開発をしたいエンジニアを募集しています!
-
additional という単語が思いつかなかったので、それっぽい単語を色々入れて試していました。 ↩︎
Discussion