REST APIを簡単にMockできるツールSmopeckの紹介

# はじめに
最近のウェブアプリではバックエンドをREST-APIとして用意し、
フロントエンドはREST-APIから引っ張ってきたデータをReactやVueといったフレームワークで描画することが多いと思います。

このようなウェブアプリを開発する際に問題となるのはバックエンドとフロントエンドを並行して開発しにくいということです。バックエンドができなければフロントエンドはどんなデータが来るのかわからず描画できませんし、バックエンドもフロントエンドからどのようなリクエストが来るか決まらないと実装ができません。

そのため、最初にREST-APIの仕様を定めて、その仕様に沿ったモックサーバを作成し、
フロントエンドはバックエンドが完成するまでそれを用いて開発を進めるということが行われます。

さてそのREST-APIの仕様とはどのように記述されるのでしょうか？

## 1. 自然言語で記述する
一番よくある場合だと思います。自然言語では非常に柔軟な仕様を記述することができます。
ただし、その解釈は一通りとは限らないので、認識のズレから不整合が起こることもあります。

## 2. gRPC等で記述する
`gRPC`の時点でREST-APIとは正確には異なりますが、機械が読める形で仕様を記述できるので認識のズレは起こりにくいです。
ただ、記述できる仕様はデータの基本的な型のみでそこまで柔軟ではありません。

また、どちらの場合であっても、モックサーバを実装するのにも多少の時間がかかります。
また、モックサーバの入出力が本当に仕様を守っているのかは検証しないことの方が多いでしょう。

私はここでの第３の選択肢となるツールSmopeckを開発しました。
https://github.com/autotaker/smopeck

ツールはまだプロトタイプですが、それなりに使えるようになってきたので紹介していきます。

# Smopeck

Smopeckの特長は以下の通りです

- 仕様を型で書くとその仕様のREST-APIをモックするサーバができます。
- 依存型や正規表現をサポートすることで柔軟な仕様が記述できます。


## デモ
### Hello World!
試しにHello Worldを作成してみましょう。

```smopeck:hello.spec
endpoint "/hello" GET {
  response : JsonResponse {
    body: String [ . = 'hello world!' ]
  }
}
```

これを入力として渡してサーバを立ち上げます。

```bash
cabal new-exec smopeck mock --host localhost --port 8888 hello.spec
```

これだけでREST-APIができています。試しにアクセスしてみるとちゃんとレスポンスが返ってきます。

```console
$ curl -s http://localhost:8888/hello | jq .
"hello world!"
```

### Profile
次は `/profile`でユーザのIDとnameを取得するAPIを作ってみましょう。
ただし、次の仕様を満たす必要があるとします。

- ユーザIDは正の整数である
- ユーザ名は英数字のみからなり１文字以上１０文字以下である。

```smopeck:profile.spec
endpoint "/profile" GET {
    response: JsonResponse {
        body: Object {
            id : Int [ . > 0 ],
            name : String [ . =~ r'[a-zA-Z0-9]{1,10}' ]
        }
    }
}
```

これだけです。`[ ]`の内側にデータが満たすべき述語がかけるので非常に直感的に書くことができます。

現在サポートしている述語演算子は 等号, 比較演算子（整数型のみ）, `=~` (文字列型のみ）です。

サーバを再起動するとREST-APIができます。

```console
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "nNgDWJho",
  "id": 93
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "aaKCZgzOLD",
  "id": 9
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "x",
  "id": 86
}
$ curl http://localhost:8888/profile -s | jq .
{
  "name": "xbk",
  "id": 61
}
```

smopeckでは仕様を満たす値が複数ある場合はそれらをランダムに生成します。

### Request Parameter
次は検索API `/search`を作ってみましょう。

仕様
- GETパラメータ`key`で検索文字列を受け取る
- 返り値はオブジェクトのリストで長さは１０未満
- リストの各オブジェクトは 整数型フィールド`id`と 文字列型フィールド`content`を持つ
- `id`フィールドの値は１以上
- `content`フィールドの値は `key`を部分文字列として含む。


この仕様をsmopeckで記述するとこうなります。

```smopeck:search.spec
endpoint "/search" GET {
    parameter: Parameter {
        query : Object {
            key : String 
        }
    },
    response: JsonResponse {
        body: Array {
            length: Int [ . >= 0, . < 10 ],
            get(i): Object {
                id: Int [ . > 0 ],
                content: String [ . =~ r'.*' + parameter.query.key + r'.*' ]
            }
        }
    }
}
```

リストは`Array`型で表現できます。`Array`型では `length`で長さの指定が
`get(i)`で、`i`番目の要素の指定ができます。

`parameter`部でGETパラメータの仕様を書くと`parameter.query.key`で利用できるのがポイントです。

実行してみましょう。

```console
$ curl http://localhost:8888/search?key=hoge -s | jq .
[
  {
    "content": "hhohh?o%hohogeho",
    "id": 69
  },
  {
    "content": "thogehhhhoh9",
    "id": 9
  },
  {
    "content": "hi_4\\hhah hohhoghoge",
    "id": 75
  },
  {
    "content": "hhoge",
    "id": 39
  },
  {
    "content": "hhshoghhoh]h,hov7hLhog>hohoge",
    "id": 15
  },
  {
    "content": "|/hhhhho3nhhogeh",
    "id": 73
  },
  {
    "content": "hohhho\"hoge",
    "id": 47
  }
]
```

生成される文字列に検索文字列が含まれていることが確認できると思います。[^1]

[^1]: 生成される文字列に`h`が多いのは現在の生成アルゴリズムの分布に偏りがあるためです。


### Form Validation
今度はPOSTを行うAPIを作ってみましょう。

Emailをアドレスを登録するAPIを作ってみることにします。

仕様
- パスは `/user/${id}/profile/email`ここで `${id}`はユーザのIDを表す１以上の整数 
- 送信データはJSONオブジェクトで、文字列型の`email`フィールドを持つ
- `email`フィールドの値はemailアドレスでなければならない。

```smopeck:form.spec
endpoint "/user/:id/profile/email" POST {
    parameter : Parameter {
        path: Object {
            id : Int [ . > 0]
        }
    },
    request: JsonRequest {
        body: Object {
            email: String [ . =~ r'[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*' ]
        }
    },
    response: JsonResponse
}
```
`parameter`部で `path`パラメータを設定すると、RESTパスの`:id`を`Int`型としてパーズしてくれます。emailアドレスは簡単な正規表現でチェックしています。[参考](https://qiita.com/sakuro/items/1eaa307609ceaaf51123)

smopeckは実際に来たリクエストが`request`部に書かれた仕様を満たしているか検査します。
検査に通ればレスポンスを返しますが、通らなければ400エラーを返します。

```console
$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@example.com" }' -L -s -w '%{http_code}\n' -o /dev/null
200
$ curl http://localhost:8888/user/100/profile/email -X POST -H 'Content-Type: application/json' -d '{ "email": "autotaker@exam@ple.com" }' -L -s -w '%{http_code}\n' -o /dev/null
400
```

# smopeckの使い道
「はじめに」でも述べましたが、smopeckはWEBアプリケーションのフロントエンドを開発する際のモックサーバとして使うことを想定していますが以下の目的でも使えるでしょう。

- 外部APIを利用するアプリケーションのテスト用のモックサーバ
- テスト用データの自動生成

# 今後
smopeckでは今後、以下の機能を実装していく予定です。

1. フロントエンドとバックエンドの通信の間に入って立ち、バックエンドで実装されていないAPIのみをモックするProxy機能
2. リクエストの仕様に従った値を自動生成してバックエンドサーバに送信し、結果がレスポンスの仕様にあっているかを自動テストする機能
3. `.spec`ファイルの構文およびサポートする演算子の拡充