こんにちは。TIG DXユニット 1 の武田です。
はじめに みなさんSwagger使ってますか?
当社でもREST APIを構築するに当たってSwaggerを導入する機会が増えています。本記事ではSwaggerを導入するに当たって、合わせて利用して便利だったツールを紹介したいと思います。
そもそもSwaggerとは? Swaggerは、OpenAPI仕様 (以下OAS)と言われる、REST APIを定義するための標準仕様にもとづいて構築された一連のオープンソースツールです。REST APIの設計、構築、文書化、および使用に役立つ機能を提供します。
提供されている主なツールは次のようなものがあります。
サードパーティ製のツール 本家からは上述のツールが提供されていますが、サードバーティ製の様々なツールが世の中には存在します。
エコシステムが成熟しているのもSwaggerを利用するメリットの1つですね。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 を利用します。定義内容はこちら 。
まずはヘルプコマンド 利用可能なオプションは次のとおりとなります。
$ 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
コマンドを実行するとモックサーバが立ち上がります。
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
ローカルから繋いでみます。 定義した通りのレスポンスが返却されていますね。
$ 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定義にもとづいてリクエストのたびにレスポンスが動的に作成されます。
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回目
$ 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回目
$ 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定義にもとづいてクエリパラメータやリクエストボディのバリデーションを行ってくれます。 返却されるエラーの詳細は公式のドキュメント を参考にしてみてください。
$ 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 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のリクエストが投げられ、レスポンスが検証されます。
$ 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レスポンスのstatus
がavailable
pending
sold
のいずれのenum値にも当てはまらない値(hoge
)を返します。
fail: body: At '/status' No enum match for: "hoge"
とログが出力され、期待通りテストが失敗していますね。
$ 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定義を一元管理し、prism
やDredd
を効果的に利用することでこのようなコストを大幅に削減でき、品質を強化できます。
ご参考になれば幸いです。
このスキーマファースト開発のためのOpenAPI(Swagger)設計規約 記事もおすすめです。