OpenAPIスキーマのあるRails製APIサーバにて、テストコードを Committee
+ Committer::Rails
で書いていたところ、 TypeError: no implicit conversion of String into Integer
というエラーが出ました。
エラーを解消するまでに少々悩んだため、メモを残します。
目次
環境
エラーを再現するための環境構築
前回の記事で使用したサーバアプリを元に、環境構築を行います。
作成するAPIの仕様
curlで
curl 'http://localhost:3001/api/schema/array/items/shops/1'
とリクエストすると、
{"apples":[{"name":"シナノゴールド","color":"黄"},{"name":"ふじ","color":"赤"}]}
というレスポンスが返ってくるようなAPIを作成します。
OpenAPIスキーマ
まずはOpenAPIを作成します。
前回の記事のOpenAPIスキーマを流用しますが、適宜 components
の parameters
や schemas
を利用しています。
openapi: 3.0.0 info: title: Rails with OpenAPI version: 0.0.1 servers: - url: http://localhost:3001 components: schemas: Apple: type: object properties: name: type: string example: シナノゴールド color: type: string example: 黄 parameters: ShopID: name: shopId in: path description: 店舗ID required: true schema: type: integer paths: /api/schema/array/items/shops/{shopId}: get: parameters: - $ref: '#/components/parameters/ShopID' responses: '200': description: successful operation content: application/json: schema: type: object properties: apples: type: array items: - $ref: '#/components/schemas/Apple'
コントローラ
続いてコントローラを作成します。
URL末尾が shopId
ど動的になっているため、 show
メソッドで定義します。
なお、今回モデルやDBの準備は省略したいので、paramsに含まれる id
は無視し、常に同じレスポンスを返すようにしています。
class Api::Schema::Array::Items::ShopsController < ApplicationController def show # parameterのidは使わない render json: { apples: [ { name: 'シナノゴールド', color: '黄' }, { name: 'ふじ', color: '赤' }, ], } end end
ルーティング
仕様に合わせたルーティングを作成します。
Rails.application.routes.draw do namespace :api do namespace :schema do namespace :array do namespace :items do resources :shops, only: [:show] end end end end end
curlで動作確認
% curl 'http://localhost:3001/api/schema/array/items/shops/1'
とすると、
{"apples":[{"name":"シナノゴールド","color":"黄"},{"name":"ふじ","color":"赤"}]}
が返ってきました。仕様通り作成できました。
テストコードの作成と実行
続いて Committee を使ってテストコード /spec/requests/api/schema/array/items/shops_controller_spec.rb
を作成します。
require 'rails_helper' RSpec.describe 'Api::Schema::Array::ItemsController', type: :request do let(:response_body) { JSON.parse(response.body) } describe 'GET /api/schema/array/items/shops/{shopId}' do it '正常系' do get '/api/schema/array/items/shops/1' assert_request_schema_confirm assert_response_schema_confirm(200) end end end
テストを実行すると、エラーで落ちました。
% bundle exec rspec spec/requests/api/schema/array/items/shops_controller_spec.rb F Failures: 1) Api::Schema::Array::Items::ShopsController GET /api/schema/array/items/shops/{shopId} 正常系 Failure/Error: assert_request_schema_confirm TypeError: no implicit conversion of String into Integer
原因
no implicit conversion of String into Integer
とあることから、当初はOpenAPIスキーマの path
で指定した shopId
が誤っていることを疑いました。
しかし、それらしい原因が分かりませんでした。
続いて、OpenAPIスキーマの仕様を見ていったところ、DataType Arrayのところで目が止まりました。
Arrays | specification | Data Types
Arrayの定義をよく見ると
type: array items: type: string
となっています。
また、 $refs
を使う場合は
type: array items: $ref: '#/components/schemas/Pet'
となっています。
一方、手元のOpenAPIスキーマをよく見ると
apples: type: array items: - $ref: '#/components/schemas/Apple'
と、 $refs
の先頭に -
が付いています。
どうやら、 items
の定義方法が誤っていたようです。
なぜ items
の先頭に -
が付けてしまったのかを考えたところ、 Mixed-Type Arraysのところで oneOf
を使った定義を見つけました。この定義をどこかで見かけたために -
を先頭につけてしまったのかもしれません。
https://swagger.io/docs/specification/data-models/data-types/#mixed-array
type: array items: oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog'
また、Mixed-Type Arraysで誤った例として
# Incorrect items: - type: string - type: integer # Incorrect as well items: type: - string - integer
も載っていました。こちらも見落としていたようです。
対応
items
の定義を修正します。
/api/schema/array/items/shops/{shopId}: get: summary: OpenAPIスキーマのArrayに関する動作検証用のAPI parameters: - $ref: '#/components/parameters/ShopID' responses: '200': description: successful operation content: application/json: schema: type: object properties: apples: type: array items: # 修正前 # - $ref: '#/components/schemas/Apple' # 修正後 $ref: '#/components/schemas/Apple'
テストコードを流すと、テストがパスしました。
% bundle exec rspec spec/requests/api/schema/array/items/shops_controller_spec.rb . Finished in 0.0461 seconds (files took 0.99509 seconds to load) 1 example, 0 failures
余談:他のツールではどのように見えるか
RubyMineの OpenAPI Specifications の場合
Rubyでの開発中にOpenAPIスキーマを書くときは、RubyMineの OpenAPI Specifications
プラグインを使っています。
OpenAPI Specifications - IntelliJ IDEs Plugin | Marketplace
OpenAPIスキーマを書くときもコードジャンプができたり、補完入力ができるためです。
例えば、こんな感じで $refs
の途中まで入力するとサジェストされます。
サジェストされたものをEnterで確定すると、 $refs
の設定値が自動的に補完・入力されます。
ただ、このプラグインを使っていてもエラーが検出できていないっぽいです。
OpenAPIスキーマのプレビューを見たところ、type: object
ではなく type: string
で認識されているようでした。
一方、正しく書いたOpenAPIスキーマの場合は、 type: object
で認識されているようです。
Swagger Editor (online) の場合
OpenAPIスキーマのエディタとして、オンラインのエディタも提供されています。
https://editor.swagger.io/
そこで、誤っている定義を貼り付けてみたところ、エラーが出ていました。
Swagger Editor をローカルで動かした場合
Swagger Editorでバリデーションするのは良さそうですが、もし仕事で使う場合はオンラインなのがネックです。
そのため、Swagger Editor をローカルで動かしてみて、同じバリデーションが走るかどうかを確認してみます。
Download OpenAPI Editor | Swagger Open Source
GithubのREADMEを読むと、公式でDockerイメージが提供されているようですので、そちらを使って起動してみます。
https://github.com/swagger-api/swagger-editor#running-the-image-from-dockerhub
# イメージをダウンロード % docker pull swaggerapi/swagger-editor # ローカルの20030 ポートで起動 % docker run -d -p 20030:8080 swaggerapi/swagger-editor
20030ポートを開き、先ほどエラーになったOpenAPIスキーマを貼り付けると、無事にエラーとなりました。
これにより、OpenAPI Specifications + Swagger Editor (ローカル) で開発できる環境ができました。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_0_openapi_server_app
プルリクはこちら。
https://github.com/thinkAmi-sandbox/rails_7_0_openapi_server_app/pull/2