Phoenix で JSON Schema を使って Validation する

JSON ベースの API では JSON Schema を使った Validation が非常に有効です。

Elixir で JSON Schema を使った JSON の妥当性検証をしてみます。

# Phoenix アプリケーションをセットアップする

`json_schema_sample` というアプリケーションを作ります。

```bash
$ mix phoenix.new json_schema_sample
```

今回は [poison](https://github.com/devinus/poison)（JSON デコード用）と [ex_json_schema](https://github.com/jonasschmidt/ex_json_schema)（JSON Schema 解釈用）というライブラリを使いますので、こちらを依存関係に追加します。

```ex:mix.exs
defmodule JsonSchemaSample.Mixfile do
  ...
  defp deps do
    [{:phoenix, "~> 1.0.2"},
     {:phoenix_ecto, "~> 1.1"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.1"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:cowboy, "~> 1.0"},
     # poison を追加
     {:poison, "~> 1.5"},
     # ex_json_schema を追加
     {:ex_json_schema, "~> 0.2.0"}]
  end
end
```

最後に依存関係をダウンロードして完了です。

```bash
$ mix deps.get
```

# JSON Schema を用意する

人物の名称、生年月日、身長を受け取る API を想定してみます。

* 名称 *name*
    * 文字列
* 生年月日 *birthdate*
    * *xxxx/xx/xx* 形式の文字列（x は数字）
    * 2015/99/99 のようなありえない年月日もひとまず許容
* 身長 *height*
    * 整数値
    * 未指定も許容

以上のルールを元に、`sample_schema.json` を作成しましょう。

```json:sample_schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "/",
  "type": "object",
  "properties": {
    "name": {
      "id": "name",
      "type": "string"
    },
    "birthdate": {
      "id": "birthdate",
      "type": "string",
      "pattern": "^[0-9]{4}/[0-9]{2}/[0-9]{2}$"
    },
    "height": {
      "id": "height",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "birthdate"
  ]
}
```

ちなみに、オリジナルの JSON Schema を作るときは [jsonschema.net](http://jsonschema.net/) のようなジェネレータを使うとラクです。

上記のファイルを `priv/schema/` に配置して完了です。

# ルーティングを設定する

JSON データを POST で受け取る API を作ります。
`router.exs` を以下のように改修しましょう。

```ex:router.exs
defmodule JsonSchemaSample.Router do
  ...
  scope "/", JsonSchemaSample do
    # :brower -> :api に変更
    pipe_through :api

    # get -> post に変更
    post "/", PageController, :index
  end
  ...
end
```

# コントローラを実装する

さて、いよいよコントローラの実装です。
POST された JSON が妥当かどうかを返却させてみます。

```ex:page_controller.ex
defmodule JsonSchemaSample.PageController do
  use JsonSchemaSample.Web, :controller

  def index(conn, params) do
    schema = Poison.decode!(File.read! "priv/schema/sample_schema.json")
      |> ExJsonSchema.Schema.resolve

    json conn, ExJsonSchema.Validator.valid?(schema, params)
  end
end
```

`sample_schema.json` を読み込んで Poison でデコードし、それを `ExJsonSchema.Schema.resolve/1` に渡しています。
これにより解釈されスキーマデータが取得できます。
あとは、`ExJsonSchema.Validator.valid?/2` で、取得したスキーマで入力された JSON（マップ）を検証します。
この関数は、妥当であれば true, 妥当でなければ false を返します。

ExJsonSchema がいかにシンプルに使えるかが、とても良くわかると思います。

なお、Phenix では `Content-type: application/json` で JSON データを送ると、自動で `params` に値をマッピングしてくれます。
ちなみに、`Content-type: plain/text` でデータを送ると `params` には何もマッピングされません。この場合は `Plug.Conn.read_body/1` にコネクション情報（*conn*）を渡すことで、テキストデータを読み取れます。


# 動作を確認してみる

curl を使って JSON データを POST してみましょう。
まずは正常パターンから

```json:data1.json
{
  "name": "johnny knoxville",
  "birthdate": "1971/03/11",
  "height": 185
}
```
```bash
$ curl -H "Content-type: application/json" -X POST -d @data1.json "localhost:4000/"
true
```

ちゃんと ture が返ってきました。
*height* ナシ版も送ってみましょう。

```json:data2.json
{
  "name": "johnny knoxville",
  "birthdate": "1971/03/11"
}
```
```bash
$ curl -H "Content-type: application/json" -X POST -d @data2.json "localhost:4000/"
true
```

これも大丈夫そうです。
次に異常パターンとして、*name* に数値を入れてみます。

```json:data3.json
{
  "name": 12345,
  "birthdate": "1971/03/11"
}
```
```bash
$ curl -H "Content-type: application/json" -X POST -d @data3.json "localhost:4000/"
false
```

異常が検知されました。
最後に *birthdate* の許容しないパターンにしてみます。

```json:data4.json
{
  "name": "johnny knoxville",
  "birthdate": "1971/aa/11"
}
```
```bash
$ curl -H "Content-type: application/json" -X POST -d @data4.json "localhost:4000/"
false
```

ちゃんと正規表現も効きました。

# 感想

* ExJsonSchema は非常にシンプルに使える
* 追加・更新処理を JSON でやり取りするような API を作る場合に有効活用できそう
* ExJsonSchema はここ最近（2015/09/30時点）開発されているようなので、今後に期待
    * 開発の手伝いをしても良さそう