committeeで、ファイル分割したOpenAPI定義を使用する方法
committeeは、OpenAPIの定義内容とAPIの挙動に乖離がないかをRSpecで検証するためのGemです。
ちなみに、Railsでの使用に対応した committee-rails というgemがあります。Rspecとcommitteeで、OpenAPIとの比較用に使うAPIのリクエスト・レスポンスの指定を簡単にやってくれるgemです。
OpenAPIの定義ファイルが巨大になる時〜
OpenAPIをYAMLに定義していくと、巨大なYAMLファイルになるので、スキーマやレスポンス毎にファイル分割したい時がよくあると思います。
例えば、こういうOpenAPI定義があるとします。
openapi.yaml
openapi: 3.0.1
info:
title: API Document
description: >-
API doc
version: 1.0.0
paths:
"api/users/{id}":
get:
responses:
"200":
description: return User info
content:
application/json:
schema: $ref: "#/components/schemas/User"
components:
schemas:
User: ./schemas/User.yaml
Userというスキーマが利用頻度が高い場合、毎回同じ内容を定義せず、このように別ファイルにするとファイルの肥大化を防げます。
(openapi.yaml
と同じ階層に schemas/
というディレクトリがある前提)
schemas/User.yaml
type: object
properties:
id:
type: integer
name:
type: string
OpenAPIの定義に基づいて、APIをRailsのコントローラーで実装するとこうなるでしょう。
class Api::UsersController < ApplicationController
def show
user = User.find(params[:id])
render json: user
end
end
これで、Swagger-UIなどでは、問題なくAPIドキュメントは表示されます。
しかし、以下のようにcommitteeを使ったRSpecを実行する場合は、どうなるでしょうか?
require "spec_helper"
RSpec.describe Api::UsersController, type: :request do
describe "GET /users/:id" do
include Committee::Rails::Test::Methods
def committee_options
@committee_options ||= { schema_path: Rails.root.join("openapi.yaml").to_s }
end
it "スキーマ定義とAPIの挙動が同じであること" do
user_id = 1
get api_user_path(user_id)
assert_response_schema_confirm
end
end
end
エラーが発生
Failure/Error: assert_response_schema_confirm
NoMethodError:
undefined method `any_of' for #<OpenAPIParser::Schemas::Reference:0x00007ffd19c6eaa8>
TL;DR
先に結論を述べると、$ref: 分割したファイルのパス#components/schemas/xxxx
のような形式で、パス指定をしないと、committee内部で、使用しているopenapi_parser
が外部ファイルのパス参照をしてくれない。
参考: https://swagger.io/docs/specification/components/
そのため$ref
のパス指定は以下のように修正
...省略
components:
schemas:
User:
$ref: "./schemas/User.yaml#/components/schemas/User"
分割したUser.yamlも、components
を一番上のキーとして修正しなければならない。
schemasUser.yaml
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
原因としては、、committeeが内部で、openapiのYAMLをパースするために使用しているopenapi_parser
の https://github.com/ota42y/openapi_parser/blob/f9b57b0faea11a39f1264d260f2bdc9a6f1c54a3/lib/openapi_parser/concerns/findable.rb#L9 で、$ref: ./schemas/User.yaml
だと、別ファイルの参照する処理が実行されないためである。
分割された定義ファイルの参照に関するPRもある。
- openapi_parser
最初から、OpenAPIのドキュメント(https://swagger.io/docs/specification/components/)を読んでいれば早く気付けたのに、回り道してGem内部の実装を読んでいた。
公式ドキュメントは、ちゃんと読みましょう(自戒)🙏
Discussion
記事の通りに
"./schemas/User.yaml#components/schemas/User"
としても動かなかったのですが、"./schemas/User.yaml#/components/schemas/User"
のようにスラッシュを # の後ろに入れたら動きました。書き方のサンプルとしては以下の yaml ファイルが参考になりました。
ご指摘ありがとうございます!記事の方も修正しておきました。