Future Tech Blog
フューチャー開発者ブログ

本当に使ってよかったOpenAPI (Swagger) ツール


こんにちは。TIG DXユニット1の武田です。

はじめに

みなさんSwagger使ってますか?

弊社でもREST APIを構築するに当たってSwaggerを導入する機会が増えています。本記事ではSwaggerを導入するに当たって、合わせて利用して便利だったツールを紹介したいと思います。

そもそもSwaggerとは?

Swaggerは、OpenAPI仕様(以下OAS)と言われる、REST APIを定義するための標準仕様にもとづいて構築された一連のオープンソースツールです。REST APIの設計、構築、文書化、および使用に役立つ機能を提供します。

提供されている主なツールは次のようなものがあります。

Name Description
Swagger Editor OASに則ったAPI仕様を書くためのエディタ
Swagger UI OASに則ったAPI仕様からドキュメントを生成するツール
Swagger Codegen OASに則ったAPI仕様からコードを生成するツール

サードパーティ製のツール

本家からは上述のツールが提供されていますが、サードバーティ製の様々なツールが世の中には存在します。

エコシステムが成熟しているのもSwaggerを利用するメリットの一つですね。
https://openapi.tools/

冒頭のとおり、このサードパーティ製のツールの中で実際に利用して良かったツールを3つご紹介したいと思います。

Stoplight Studio

https://stoplight.io/studio/

1つ目のツールは「Stoplight Studio」というAPI仕様を記載するためのGUIエディタとなります。

今までSwagger Editorを利用してYAMLを書いていたそこのみなさん、YAML筋力はもう必要ありません。

Design APIs 10x faster の謳い文句どおり、Stoplight Studioを使えばGUIで直感的に、高速にAPI仕様を記述することができます。

主な特徴

主な特徴としては次のようなものが挙げられます。

  • 無料
  • WEB、バイナリ(Windows、Mac、Linux)として配布
  • OpenAPI v2 & v3に対応
  • Git連携
  • Prismと呼ばれるモックサーバ(後述)を統合
  • ドキュメントへの変換に対応
  • リアルタイムでLintエラーを表示

筆者はもうこのエディタなしではSwaggerを書けない体になりました。WEBから簡単に試すことができるので実際に使ってみるのが一番だと思います。
https://stoplight.io/p/studio/gh/stoplightio/studio

Prism

https://stoplight.io/open-source/prism

2つ目のツールは「Prism」というStoplight Studioと同じくStoplight社が提供するOSSのモックサーバです。

コマンドラインからOAS定義を読み込むことで簡単にAPIのモックサーバが起動できます。例えばサーバ(API)側ができていない状態で、クライアント側の開発を進めるケースなどでは非常に有用ですね。

主な特徴

主な特徴としては次のようなものが挙げられます。

  • OSS
  • OpenAPI v2 & v3に対応
  • Nodeモジュール、バイナリ(Windows、Mac、Linux)、Dockerイメージとして配布
  • ダイナミックレスポンス対応
  • リクエストのバリデーション対応
  • CORS対応

apisproutなど他のモックサーバも多数存在しますが、ランタイムなしで利用できる点やダイナミックレスポンス、CORS対応等、地味に嬉しい機能があり、お気に入りです。

使ってみた

今回はDockerイメージ利用してみます。 サンプルのOAS定義としてSwagger Petstoreを利用します。定義内容はこちら

まずはヘルプコマンド

利用可能なオプションは次のとおりとなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker run stoplight/prism:3 mock -h
prism mock <spec>

Start a mock server with the given spec file

Positionals:
spec Path to a spec file. Can be both a file or a fetchable resource on the web. [string] [required]

Options:
--version Show version number [boolean]
--help Show help [boolean]
--port, -p Port that Prism will run on. [number] [required] [default: 4010]
--host, -h Host that Prism will listen to. [string] [required] [default: "127.0.0.1"]
--dynamic, -d Dynamically generate examples. [boolean] [default: false]
--cors Enables CORS headers. [boolean] [default: true]
--multiprocess, -m Forks the http server from the CLI for faster log processing. [boolean] [default: true]

サーバ起動

引数にOAS定義を指定して prism mock コマンドを実行するとモックサーバが立ち上がります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
docker run --rm -it -p 4010:4010 stoplight/prism:3 mock -h 0.0.0.0 https://petstore.swagger.io/v2/swagger.json
[CLI] … awaiting Starting Prism…
[HTTP SERVER] ℹ info Server listening at http://0.0.0.0:4010
[CLI] ● note POST http://0.0.0.0:4010/pet
[CLI] ● note PUT http://0.0.0.0:4010/pet
[CLI] ● note GET http://0.0.0.0:4010/pet/findByStatus
[CLI] ● note GET http://0.0.0.0:4010/pet/findByTags
[CLI] ● note GET http://0.0.0.0:4010/pet/{petId}
[CLI] ● note POST http://0.0.0.0:4010/pet/{petId}
[CLI] ● note DELETE http://0.0.0.0:4010/pet/{petId}
[CLI] ● note POST http://0.0.0.0:4010/pet/{petId}/uploadImage
[CLI] ● note GET http://0.0.0.0:4010/store/inventory
[CLI] ● note POST http://0.0.0.0:4010/store/order
[CLI] ● note GET http://0.0.0.0:4010/store/order/{orderId}
[CLI] ● note DELETE http://0.0.0.0:4010/store/order/{orderId}
[CLI] ● note POST http://0.0.0.0:4010/user
[CLI] ● note POST http://0.0.0.0:4010/user/createWithArray
[CLI] ● note POST http://0.0.0.0:4010/user/createWithList
[CLI] ● note GET http://0.0.0.0:4010/user/login
[CLI] ● note GET http://0.0.0.0:4010/user/logout
[CLI] ● note GET http://0.0.0.0:4010/user/{username}
[CLI] ● note PUT http://0.0.0.0:4010/user/{username}
[CLI] ● note DELETE http://0.0.0.0:4010/user/{username}
[CLI] ▶ start Prism is listening on http://0.0.0.0:4010

ローカルから繋いでみます。 定義した通りのレスポンスが返却されていますね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ curl -s -D /dev/stderr -X GET -H "Accept:application/json" http://localhost:4010/pet/0001 | json_pp
HTTP/1.1 200 OK
access-control-allow-origin: *
content-type: application/json; charset=utf-8
content-length: 138
Date: Fri, 27 Sep 2019 10:25:48 GMT
Connection: keep-alive

{
"status" : "available",
"photoUrls" : [
"string"
],
"id" : 0,
"name" : "doggie",
"category" : {
"name" : "string",
"id" : 0
},
"tags" : [
{
"id" : 0,
"name" : "string"
}
]
}

ダイナミックレスポンス

モックサーバ起動時に-dオプションを付与すると、OAS定義にもとづいてリクエストのたびにレスポンスが動的に作成されます。

1
$ docker run --rm -it -p 4010:4010 stoplight/prism:3 mock -h 0.0.0.0 -d https://petstore.swagger.io/v2/swagger.json

1回目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ curl -s -D /dev/stderr -X GET -H "Accept:application/json" http://localhost:4010/pet/0001?hoge=dow | json_pp
HTTP/1.1 200 OK
access-control-allow-origin: *
content-type: application/json; charset=utf-8
content-length: 338
Date: Fri, 27 Sep 2019 10:38:36 GMT
Connection: keep-alive

{
"tags" : [
{
"name" : "aliquip tempor",
"id" : 63211888
},
{
"name" : "in enim dolor",
"id" : 79883460
},
{
"name" : "eiusmod ",
"id" : 17183756
}
],
"category" : {
"name" : "fugiat",
"id" : -44395203
},
"id" : -79576346,
"status" : "available",
"name" : "nostrud",
"photoUrls" : [
"amet eiusmod Duis deserunt sunt",
"dolor",
"Duis non reprehenderit",
"laboris mollit officia consectetur"
]
}

2回目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ curl -s -D /dev/stderr -X GET -H "Accept:application/json" http://localhost:4010/pet/0001?hoge=dow | json_pp
HTTP/1.1 200 OK
access-control-allow-origin: *
content-type: application/json; charset=utf-8
content-length: 274
Date: Fri, 27 Sep 2019 10:38:39 GMT
Connection: keep-alive

{
"name" : "ex consequat ea irure",
"status" : "available",
"tags" : [
{
"id" : 39284365,
"name" : "elit Duis nostrud"
}
],
"category" : {
"id" : 39510092,
"name" : "quis veniam ipsum Excepteur"
},
"id" : 97837350,
"photoUrls" : [
"reprehenderit exercitation commodo dolore",
"consectetur",
"sint",
"consequat"
]
}

バリデーション

リクエストボディを指定せずに POST: http://0.0.0.0:4010/petを投げてみるとエラーが返却されます。
このようにOAS定義にもとづいてクエリパラメータやリクエストボディのバリデーションを行ってくれます。
返却されるエラーの詳細は公式のドキュメントを参考にしてみてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl -s -D /dev/stderr -X POST -H "Accept:application/json" http://localhost:4010/pet | json_pp
HTTP/1.1 422 Unprocessable Entity
access-control-allow-origin: *
content-type: application/problem+json
content-length: 350
Date: Fri, 27 Sep 2019 10:41:23 GMT
Connection: keep-alive

{
"validation" : [
{
"message" : "Body parameter is required",
"severity" : "Error",
"code" : "required"
}
],
"detail" : "Your request body is not valid and no HTTP validation response was found in the spec, so Prism is generating this error for you.",
"type" : "https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY",
"title" : "Invalid request body payload",
"status" : 422
}

CORS

Prismはデフォルトで、全てのメソッドと全てのオリジンを許可するため、全てのプリフライトリクエストを204でハンドリングします。

ローカルで(Webpack Dev Server等を利用して)WEB開発をしているときに、プロキシを設定したりしなくて済むのは、嬉しいですね。

Dredd

https://dredd.readthedocs.io

最後に紹介するツールは「Dredd」というOAS定義と実際のAPIサーバの検証を行うコマンドラインベースのテストツールになります。

要はAPIのレスポンスがOAS定義通りだよね?というのを確認してくれるツールです。もともとはAPI Blueprintに対応していたツールですが、OpenAPIにも対応がなされました。

主な特徴

主な特徴としては次のようなものが挙げられます。

  • OpenAPI v2 & v3に対応(ただしv3はExperimental)
  • Nodeモジュール、Dockerイメージとして配布
  • テスト時の前処理、後処理をさまざまな言語(Go, Node.js, Perl, Python, Ruby, etc…)で定義可能

使ってみた

テスト対象のAPIサーバはlocalhost:4010で動いている前提とします。

テスト仕様書となるOAS定義として今回もSwagger Petstoreを利用したいところですが、そのまま利用するには色々と問題があるみたいなので、Petstoreを修正した簡易版のOAS定義を作成し利用します。

IDをキーにペットを取得するAPI、更新するAPIの2APIを定義しています。

  • GET : /pet/{petId}
  • POST: /pet/${petId}
swagger.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
swagger: "2.0"
info:
title: Swagger Petstore
version: 1.0.2
schemes:
- http
paths:
"/pet/{petId}":
get:
summary: Find a pet by ID
description: Returns a single pet
operationId: getPetById
produces:
- application/json; charset=utf-8
parameters:
- name: petId
in: path
required: true
type: integer
format: int64
x-example: 99999
responses:
"200":
description: successful operation
schema:
"$ref": "#/definitions/Pet"
post:
summary: Update a pet by ID
description: ""
operationId: updatePet
consumes:
- application/json
produces:
- application/json; charset=utf-8
parameters:
- name: petId
in: path
required: true
type: integer
format: int64
x-example: 99999
- name: body
in: body
required: true
schema:
type: object
properties:
name:
type: string
example: pochi
responses:
"200":
description: successful operation
schema:
"$ref": "#/definitions/Pet"
definitions:
Pet:
type: object
required:
- name
- photoUrls
properties:
id:
type: integer
format: int64
example: 99999
name:
type: string
example: doggie
photoUrls:
type: array
items:
type: string
example: http://example.com
status:
type: string
enum:
- available
- pending
- sold
example: sold

正常系

OAS定義と実際のAPIサーバのホストを引き数にdredd コマンドを実行すると2本のAPIのリクエストが投げられ、レスポンスが検証されます。

1
2
3
4
5
$ dredd swagger.yaml localhost:4010 -h "Accept:application/json"
pass: GET (200) /pet/99999 duration: 26ms
pass: POST (200) /pet/99999 duration: 9ms
complete: 2 passing, 0 failing, 0 errors, 0 skipped, 2 total
complete: Tests took 38ms

異常系

テスト対象のAPIサーバのロジックを修正し、OAS定義と異なるレスポンスを返却するようにしてみましょう。
今回は、GETレスポンスのstatusavailable pending soldのいずれのenum値にも当てはまらない値(hoge)を返します。

fail: body: At '/status' No enum match for: "hoge"とログが出力され、期待通りテストが失敗していますね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$ dredd swagger.yml localhost:4010 -h "Accept:application/json" 
fail: GET (200) /pet/99999 duration: 29ms
pass: POST (200) /pet/99999 duration: 11ms
info: Displaying failed tests...
fail: GET (200) /pet/99999 duration: 29ms
fail: body: At '/status' No enum match for: "hoge"

request:
method: GET
uri: /pet/99999
headers:
Accept: application/json
User-Agent: Dredd/12.0.3 (Darwin 18.2.0; x64)

body:



expected:
headers:
Content-Type: application/json; charset=utf-8

body:
{
"name": "doggie",
"photoUrls": [
"http://example.com"
],
"id": 99999,
"status": "sold"
}
statusCode: 200
bodySchema: {"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64","examples":[99999]},"name":{"type":"string","examples":["doggie"]},"photoUrls":{"type":"array","items":{"type":"string","examples":["http://example.com"]}},"status":{"type":"string","enum":["available","pending","sold"],"examples":["sold"]}}}


actual:
statusCode: 200
headers:
access-control-allow-origin: *
content-type: application/json; charset=utf-8
content-length: 79
date: Sat, 28 Sep 2019 05:20:01 GMT
connection: close

bodyEncoding: utf-8
body:
{
"id": 99999,
"name": "doggie",
"photoUrls": [
"http://example.com"
],
"status": "hoge"
}



complete: 1 passing, 1 failing, 0 errors, 0 skipped, 2 total
complete: Tests took 44ms

このようにDreddを利用すれば、実際のAPIサーバがOAS定義に則ったレスポンスを返却しているかを検証することができます。
さらに今回は触れませんでしたが、テストの前処理、後処理等でDBのクリーンアップ、テストデータの投入を行うなどすれば、E2EのCIを実現することができます。

もともとAPI Blueprint用のツールだったこともあり、OpenAPIの扱いで筋力が必要なシーンが少なからずありますが、このあたりの泥臭い話は別途記載できればと思います。

各種ツールの統合

標準的な設計・開発プロセスにご紹介したツールを統合すると次のような形になります。

みなさんもクライアントサイドとサーバサイドの結合テストにおいてインターフェースの齟齬による苦労をした経験はあるかと思います。

OAS定義を一元管理し、prismDreddを効果的に利用することでこのようなコストを大幅に削減でき、品質を強化することができます。

ご参考になれば幸いです。


関連記事: