Hello, OpenAPI - PrismでPetstoreのモックサーバを立てる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32540/85eed458-ab81-3965-9cb4-29bc27388a88.png" width="360">

# はじめに

OpenAPI 界隈では `Hello World` の代わりに[ペットストア](https://petstore.swagger.io/#/pet/addPet)を開業することになっているらしいのでやってみた（みんな知ってる `petsoter.yaml` から `Prism` でモックサーバを立ち上げて遊びます） 

# Prism 

- [Prism](https://github.com/stoplightio/prism) は、OpenAPI仕様の `yaml` や `json` を読み込んでモックサーバを立ててくれます
- モックサーバに向かってHTTPリクエスト投げると、それなりにいい感じの応答をしてくれます
- モックなんでロジックはありませんが、HTTPヘッダ・認証・データのフォーマットチェックなどそれなりにやってくれます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32540/250b07de-baee-c3f7-c9be-d020b2b8fd53.png" width="120">

# `petsoter.yaml` を読み込みつつ `Prism` を立ち上げる

- おもむろに `docker` でいきます

```shell
$ docker run --rm -it -p 4010:4010 stoplight/prism:3 mock -d -h 0.0.0.0 https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml

[1:54:08 AM] › [CLI] …  awaiting  Starting Prism…
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet
[1:54:09 AM] › [CLI] ℹ  info      PUT        http://0.0.0.0:4010/pet
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/findByStatus
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/findByTags
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/303
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet/174
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/pet/790
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet/574/uploadImage
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/store/inventory
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/store/order
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/store/order/2
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/store/order/et
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user/createWithArray
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user/createWithList
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/login?username=et&password=libero
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/logout
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/voluptatibus
[1:54:09 AM] › [CLI] ℹ  info      PUT        http://0.0.0.0:4010/user/reiciendis
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/user/quae
[1:54:09 AM] › [CLI] ▶  start     Prism is listening on http://0.0.0.0:4010
```
- `-d` つけておくと、ランダムにそれっぽいレスポンスデータを返してくれる

# `curl` 投げつける

## `GET` する

```json
$ curl -s -H 'api_key: special-key' -H "Accept: Application/json" localhost:4010/pet/1 | json_pp
{
   "category" : {
      "id" : 7155740,
      "name" : "mollit in"
   },
   "id" : 87883213,
   "name" : "eiusmod dolor in sed id",
   "photoUrls" : [
      "est Ut cupidatat incididunt tempor",
      "ullamco ut ea ex veniam",
      "aliquip mollit proident sit",
      "minim ullamco id et",
      "ut labore non magna"
   ],
   "status" : "sold",
   "tags" : [
      {
         "id" : 35811162,
         "name" : "et cupidatat"
      },
      {
         "id" : -26616885,
         "name" : "sint ut non"
      }
   ]
}
```

- `-H 'api_key: special-key'` しないと認証できない[^1]

[^1]: 実はこのキー文字列はなんでもよい

```json:APIキーがないと
$ curl -s -H "Accept: Application/json" localhost:4010/pet/1 | json_pp
{
   "detail" : "Your request does not fullfil the security requirements and no HTTP unauthorized response was found in the spec, so Prism is generating this error for you.",
   "status" : 401,
   "title" : "Invalid security scheme used",
   "type" : "https://stoplight.io/prism/errors#UNAUTHORIZED"
}
```

- `-H "Accept: Application/json"` としないと `xml` でレスポンスが返ってくる

```xml:Acceptを指定しないと
$ curl -X GET -H 'api_key: special-key' -H 'Content-Type: application/json' localhost:4010/pet/414
<xml><id>0</id><category><id>0</id><name>string</name></category><name>doggie</name><photoUrls>string</photoUrls><tags><id>0</id><name>string</name></tags><status>available</status></xml>d030
```

## `POST` する

- `POST` はデフォルトで認証周りが `OAuth` になって面倒。今回のポイントはそこじゃないので、認証をスキップするように `petstore.yaml` を書き換えます。大胆にコメントアウト
- ちなみにここ `securityDefinitions:` でリクエストのヘッダに渡すべきAPIキーの設定もされている

```yaml:petstore.yaml
securityDefinitions:
  petstore_auth:
#    type: oauth2
#    authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
#    flow: implicit
#    scopes:
#      'write:pets': modify pets in your account
#      'read:pets': read your pets
  api_key:
    type: apiKey
    name: api_key
    in: header
```

- さらに、`200 OK` の処理が定義されていないようなので（結構いいかげんやな..）、付け足しておきます
- `/pet:` の `responses:` に`'200':` を設定してみます

```yaml:petstore.yaml
      responses:
        '200':
          description: A pet added
          schema:
            type: array
            items:
              $ref: '#/definitions/Pet'
        '405':
          description: Invalid input
```

- `petstore.yaml` を読み込みます[^2]

```
$ docker run --rm -it -p 4010:4010 -v $PWD:/tmp stoplight/prism:3 mock -d -h 0.0.0.0 /tmp/petstore.yaml

[1:54:08 AM] › [CLI] …  awaiting  Starting Prism…
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet
[1:54:09 AM] › [CLI] ℹ  info      PUT        http://0.0.0.0:4010/pet
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/findByStatus
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/findByTags
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/pet/303
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet/174
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/pet/790
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/pet/574/uploadImage
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/store/inventory
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/store/order
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/store/order/2
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/store/order/et
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user/createWithArray
[1:54:09 AM] › [CLI] ℹ  info      POST       http://0.0.0.0:4010/user/createWithList
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/login?username=et&password=libero
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/logout
[1:54:09 AM] › [CLI] ℹ  info      GET        http://0.0.0.0:4010/user/voluptatibus
[1:54:09 AM] › [CLI] ℹ  info      PUT        http://0.0.0.0:4010/user/reiciendis
[1:54:09 AM] › [CLI] ℹ  info      DELETE     http://0.0.0.0:4010/user/quae
[1:54:09 AM] › [CLI] ▶  start     Prism is listening on http://0.0.0.0:4010
```

[^2]: Prismは `petstore.yaml` を書き換えるとオートリロードしてくれるので通常はコンテナの再起動は不要

- ここまできたら、あとは[ここで定義されている](https://petstore.swagger.io/#/pet/addPet)フォーマットで JSON を用意して

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

- 投げつけます

```json
$ curl -X POST -H "accept: application/json" -s "localhost:4010/pet" -H  "Content-Type: application/json" -d @pet.json | json_pp
[
   {
      "category" : {
         "id" : 0,
         "name" : "string"
      },
      "id" : 0,
      "name" : "doggie",
      "photoUrls" : [
         "string"
      ],
      "status" : "available",
      "tags" : [
         {
            "id" : 0,
            "name" : "string"
         }
      ]
   }
]
```

- `-H  "Content-Type: application/json"` がないとエラーになる

```json:Content-Typeがないと
$ curl -X POST -H "accept: application/json" -s "localhost:4010/pet" -d @pet.json | json_pp
{
   "detail" : "Your request is not valid and no HTTP validation response was found in the spec, so Prism is generating this error for you.",
   "status" : 422,
   "title" : "Invalid request body payload",
   "type" : "https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY",
   "validation" : [
      {
         "code" : "required",
         "location" : [
            "body"
         ],
         "message" : "should have required property 'name'",
         "severity" : "Error"
      },
      {
         "code" : "required",
         "location" : [
            "body"
         ],
         "message" : "should have required property 'photoUrls'",
         "severity" : "Error"
      }
   ]
}
```

- サーバ側もこんな `200 OK` な感じ。よしとしましょう
 
```
[1:30:59 AM] › [HTTP SERVER] post /pet ℹ  info      Request received
[1:30:59 AM] › [NEGOTIATOR] ℹ  info      Request contains an accept header: application/json
[1:30:59 AM] › [VALIDATOR] ✔  success   The request passed the validation rules. Looking for the best response
[1:30:59 AM] › [NEGOTIATOR] ✔  success   Found a compatible content for application/json
[1:30:59 AM] › [NEGOTIATOR] ✔  success   Responding with the requested status code 200
```

- さっきのところで、認証をコメントアウトしないとこんな感じになるでしょう

```json:認証をコメントアウトしないと
$ curl -X POST -d @pet.json -s -H 'api_key: special-key' -H "Accept: Application/json" localhost:4010/pet/1 | json_pp
{
   "detail" : "Your request does not fullfil the security requirements and no HTTP unauthorized response was found in the spec, so Prism is generating this error for you.",
   "headers" : {
      "WWW-Authenticate" : "OAuth2"
   },
   "status" : 401,
   "title" : "Invalid security scheme used",
   "type" : "https://stoplight.io/prism/errors#UNAUTHORIZED"
}
```

- また `'200':` を設定しないとこんなかんじになるでしょう。特に問題はないですが、成功した雰囲気がでません

```json:200を設定しないと
$ curl -X POST -H "accept: application/json" -s "localhost:4010/pet" "accept: application/json" -H  "Content-Type: application/json" -d @pet.json | json_pp
{
   "detail" : "",
   "status" : 500,
   "title" : "No response in the range 200-299 defined",
   "type" : "https://stoplight.io/prism/errors#NO_SUCCESS_RESPONSE_DEFINED"
}
```

よし、だいたいわかった。先に進めそうです。

# 参考にしたサイト

- [PrismLibrary](https://github.com/PrismLibrary)/[Prism](https://github.com/PrismLibrary/Prism)
- [Steplight](https://meta.stoplight.io/docs/prism/README.md)
- [本当に使ってよかったOpenAPI (Swagger) ツール](https://future-architect.github.io/articles/20191008/)
- [OpenAPIを使ってモックサーバーを起動する](https://qiita.com/itouuuuuuuuu/items/f5f6dd31b9e9038e08af)