110
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swaggerで定義したAPIドキュメントとAPIレスポンスの差異をなくす

Last updated at Posted at 2017-12-21

はじめに

VASILYでは、API開発を始める前に、Swaggerを用いてAPIドキュメントを作成しています。
APIドキュメントを作成した後、実際のAPIレスポンスを修正したがドキュメントの更新を忘れ、ドキュメントの定義と実際のレスポンスの間に差異が生じてしまうということがありました。

そこで、今回はcommitteeというgemを用いて、Swaggerで定義したAPIドキュメントと実際のAPIレスポンスとの差異を検知する方法をご紹介します。

committeeとは

committeeは、実際のAPIリクエストやレスポンスがスキーマ定義にそっているかをチェックすることができるgemです。
Rackのミドルウェアとして動作します。
バージョン2.0からはJSON Schemaだけでなく、OpenAPI2.0(Swagger)もサポートされるようになったため今回はこちらのgemを使用します。

committeeのインストール

Gemfileに追記します。

gem 'committee'

APIドキュメンテーションを用意する

Swaggerを書く

SwaggerでAPIドキュメントを記述します。
Swaggerの導入方法、書き方については、下記の記事を参考にして下さい。

開発効率を上げる!Swaggerで作るWEB APIモック

Swaggerの記法まとめ

SwaggerはYAMLとJSONで書くことができます。
今回は、YAMLを採用し、公式ドキュメントでサンプルとして使われているPetstoreAPIを簡略化したものをサンプルAPIドキュメントとして用います。

# pet.yaml
swagger: "2.0"
info:
  description: "これはペットストアに関するAPIです。"
  version: "1.0.0"
  title: "Petstore API"
  termsOfService: "http://swagger.io/terms/"
  contact:
    email: "apiteam@swagger.io"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
produces:
  - "application/json"
consumes:
  - application/x-www-form-urlencoded
paths:
  /pets/{petId}:
    get:
      summary: "ペット情報API"
      description: "指定されたpetIdの情報を返します"
      operationId: 'get_pet_by_id'
      tags:
      - 'pet'
      parameters:
      - name: "petId"
        in: "path"
        description: "取得したいペットのID"
        required: true
        type: "integer"
      responses:
        200:
          description: "成功時のレスポンス"
          schema:
            $ref: '#/definitions/Pet'

definitions:
  Pet:
    type: object
    required:
     - id
     - name
    properties:
      id:
        type: "integer"
      name:
        type: "string"
        example: "doggie"

YAMLをJSONに変換

YAMLで書いたSwaggerをJSONに変換します。
変換には、swagger-codegenを使用します。
swagger-codegenは、Swaggerで書かれたスキーマ定義からクライアントやサーバーのコードを生成するツールです。
多くの言語やフレームワークに対応しており、Rubyのモックコードも生成できます。
swagger-codegenを使用してYAMLをJSONに変換します。

今回はMacのローカルで生成を行うのでhomebrewでswagger-codegenを入れます。

# swagger-codegenをインストール
% brew install swagger-codegen

# yamlからjsonへ変換
% swagger-codegen generate -i pet.yaml -l swagger

作成されたJSONが下記になります。

{
  "swagger" : "2.0",
  "info" : {
    "description" : "これはペットストアに関するAPIです。",
    "version" : "1.0.0",
    "title" : "Petstore API",
    "termsOfService" : "http://swagger.io/terms/",
    "contact" : {
      "email" : "apiteam@swagger.io"
    },
    "license" : {
      "name" : "Apache 2.0",
      "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "consumes" : [ "application/x-www-form-urlencoded" ],
  "produces" : [ "application/json" ],
  "paths" : {
    "/pets/{petId}" : {
      "get" : {
        "tags" : [ "pet" ],
        "summary" : "ペット情報API",
        "description" : "指定されたpetIdの情報を返します",
        "operationId" : "get_pet_by_id",
        "parameters" : [ {
          "name" : "petId",
          "in" : "path",
          "description" : "取得したいペットのID",
          "required" : true,
          "type" : "integer"
        } ],
        "responses" : {
          "200" : {
            "description" : "成功時のレスポンス",
            "schema" : {
              "$ref" : "#/definitions/Pet"
            }
          }
        }
      }
    }
  },
  "definitions" : {
    "Pet" : {
      "type" : "object",
      "required" : [ "id", "name" ],
      "properties" : {
        "id" : {
          "type" : "integer"
        },
        "name" : {
          "type" : "string",
          "example" : "doggie"
        }
      }
    }
  }
}

request specを書く

APIドキュメントにもとづいたAPIを作成します。
今回はわかりやすくするために、swagger-codegenを使ってモックコードを生成せずに、手動でRailsのコードを作成します。

config/routes.rb に/pets/#{pet_id}のルーティングを追加し、JSONを返すだけのControllerを作成します。

# config/routes.rb
resources :pets, only: [:show]
# app/controllers/pets_controller.rb
class PetsController < ApplicationController
  def show
    render json: { id: 1, name: 'pochi' }
  end
end

APIのレスポンスをテストするために、request specを書きます。
request specは、クライアント側の動作や振る舞い、特定のリクエストに対するHTTPレスポンスをテストします。
詳しい説明については、rspecのREADMEを参考にしてください。

PetstoreAPIのrequest specを作成します。

% bundle exec rails g rspec:integration Pet

上記のコマンドを実行すると、下記のようなspecが生成されます。

# spec/requests/pets_spec.rb
require 'rails_helper'

RSpec.describe "Pets", type: :request do
  describe "GET /pets" do
    it "works! (now write some real specs)" do
      get pets_path
      expect(response).to have_http_status(200)
    end
  end
end

このrequest specに、committeeの設定を追記します。
今回はわかりやすさを重視して、specファイル内にcommitteeの設定を書きます。
committeeの設定を追加すると下記のようになります。

# spec/requests/pet_spec.rb
require 'rails_helper'

RSpec.describe "Pets", type: :request do
  include Committee::Test::Methods
  include Rack::Test::Methods

  def committee_schema
    @committee_schema ||=
      begin
        driver = Committee::Drivers::OpenAPI2.new
        schema = JSON.parse(File.read(schema_path))
        driver.parse(schema)
      end
  end

  def schema_path
    Rails.root.join('swagger.json') 
  end

  describe "GET /pets",  type: :request do
    it "レスポンスがAPIドキュメントと一致する" do
      get '/pets/1'
      assert_schema_conform
    end
  end
end

schema_pathメソッドとcommittee_schemaメソッドを追加しました。
どちらも、committeeのメソッドをオーバーライドしています。

schema_pathは、API定義のJSONが置かれているパスを記述します。
committeeのschema_pathは、使用する際にオーバライドする必要があり、オーバーライドしないとエラーが返ります。
schema_path

committee_schemaは、OpenAPI Specをパースするためにオーバーライドします。
このメソッドはAPIドキュメントを読み込む際に使用されます。
driverを指定しないと、JSON Hyper Schemaのドライバが使われます。
今回は、Swaggerで定義したOpenAPI Specをパースするために、OpenAPIのドライバを使用したいので、committee_schemaメソッドをオーバーライドします。
committee_schema

  def committee_schema
    @committee_schema ||=
      begin
        driver = Committee::Drivers::OpenAPI2.new
        schema = JSON.parse(File.read(schema_path))
        driver.parse(schema)
      end
  end

テスト内にassert_schema_confirmを記述することで、APIドキュメントと実際のレスポンスが、一致しているかどうかのテストが実行されます。

APIドキュメント内で、requiredに記述した必須カラムを記述していない場合は、下記のようなエラーが表示されます。

 1) Pets GET /pets レスポンスがAPIドキュメントと一致する
     Failure/Error: assert_schema_conform

     Committee::InvalidResponse:
       Invalid response.

       #: failed schema #/properties//pets/{petId}/properties/GET: "name" wasn't supplied.
     # ./spec/requests/pets_spec.rb:22:in `block (3 levels) in <top (required)>'

最後に

Swaggerと実際のAPIレスポンスを、人が都度修正する運用は辛いですし、どうしても差異が発生しやすくなります。
committeeとrequest specを用いて、ドキュメントとの差異を防いでいきたいです。

参考

スキーマファースト開発のススメ
Web APIのレスポンスJSONをCommittee + OpenAPIでバリデーションして仕様と実装の乖離を防ぐ
JSON Validation by Committee

110
75
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
110
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?