JSON Schemaを上手く運用出来そうなprmdとその周りのお話

最近APIのバリデーションを行うのに[JSON Schema](http://json-schema.org/)を使おうという話をよく聞くのですが
じゃあやってみようとすると、単体のJSONのバリデーションを行うことは出来るけれど
それが実際の運用に適用できるようにするためには、
それなりに考えなくてはいけないようです。

# JSON Schemaって?

JSON Schemaは、文字の通りJSONのスキーマ定義を明確にするものです。
2014/09/07現在、IETFにdraft v4が3つに分割されて公開されています。

* [JSON Schema Core](http://json-schema.org/latest/json-schema-core.html)
* [JSON Schema Validation](http://json-schema.org/latest/json-schema-validation.html)
* [JSON Hyper-Schema](http://json-schema.org/latest/json-schema-hypermedia.html)

それで、単体のJSONファイルの確認ならばCoreだけで十分なのですが
WebサービスのAPIを管理しようとなると、
JSON Hyper-Schemaのほうで定義されている`links`を使って、ひとつのURIに対して複数の説明をつけたりすることになります。

# 実運用で必要なこと

そもそも、APIサーバーを運用するのに当たって
**【APIドキュメントを作成する】**というのが必須になってきます。

[前](http://qiita.com/taizo/items/0181f5ea641e2f91d0a5)に書いた[API Blueprint](http://apiblueprint.org/)の場合は
Markdown記法でドキュメントを記載することができます。

しかも、API Blueprintの場合は、中央データとしてのJSONを作成し、
そこからモックサーバーを立ち上げたり、
Restクライアントの[Postman](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm)に
インポートするためのJSONを作成を作成するライブラリが用意されてたりします。

## APIドキュメントと実際のコードの差異をなくす必要がある

ところが、そのAPI BluePrintの場合、
レスポンスフォーマットがいわゆるMarkdownのcode(バッククォートで書くやつ)で
レスポンス例を書くだけなので、単体ではバリデーションのテストが出来なかったりします。

レスポンスの検証が的確に出来ないと何に困るかというと
ドキュメントに書かれた項目がサーバーに正しく実装されていない可能性が出てきてしまい
ドキュメントに書いてある項目が実際に返ってこなかったり、
逆に返ってこないはずのものが返ってきてしまったりします。

そのために、単純にエクセルなどでドキュメントを作成した場合に
仕様変更があった場合に、そのドキュメントの整合性を保つのに
ものすごい労力を使ったりします。

なので、簡単に**【JSONのバリデーションを行えるようにする】**のが必要になってきます。

JSON Schemaの場合、もともとがJSONの構文チェックを行うのが目的なので
Blueprintの比べて、バリデーションの機能が豊富なので、
バリデーションチェックの問題に対応しやすくなります。

## すぐに大規模化するSchemaファイル

ところがWebサーバーの返すAPIの形式はURIごとに存在します。
これをすべてJSON Schemaで定義しようとすると、`URI` × `利用するメソッド`ごとに
用意しなければいけなくなります。さすがにそれはしんどい。

そこで[JSON Hyper-Schema](http://json-schema.org/latest/json-schema-hypermedia.html)を使うことになります。

例えば、JSON Hyper-Schemaにある"links"を使う場合
URIやリクエストメソッドによって、リクエストの値やレスポンスの結果を変えることが出来ます。

ただ、そうなるとウェブサービスの規模が大きくなると
schema.jsonの管理がどんどん複雑になっていきます。

そこでJSON Schema管理ツールである[prmd](https://github.com/interagent/prmd)の出番になります。

# prmd

使い方は [prmd](https://github.com/interagent/prmd)のREADMEを参照するのが一番手っ取り早いので、ここでは省略。

ざっくり利用する流れを書くと
リソースごとにyaml、もしくはjsonファイルでScaffoldを作ってくれる`init`。
リソースごとのファイルを1つのJSON Schemaにマージすることが出来る`combine`。
JSON Schemaを検証する`verify`
JSON SchemaからAPIドキュメントを生成する`doc`と`render`
があります。

READMEの流れを追っていけば、簡単にAPIドキュメント生成が出来ます。

## Validationはどう行うか?

ところが、困ったことに今度はJSON Schemaファイルを作成できたのに
バリデーションに一手間が必要になります。
`prmd init app > schemata/app.json` `prmd init user > schemata/user.json`でschemataファイルを作成して
`prmd combine`の場合、作成されるschema.jsonのpropertiesの値は以下のようになります。

```json:schema.json(前後省略)
{
  "properties": {
    "app": {
      "$ref": "#/definitions/app"
    },
    "user": {
      "$ref": "#/definitions/user"
    }
  }
}
```

このschema.jsonでは、以下のようなJSONのみをチェックすることが出来ます。

```json
{
  "app": {
    "id": "01234567-89ab-cdef-0123-456789abcdef",
    "created_at": "2012-01-01T12:00:00",
    "updated_at": "2012-01-01T12:00:00"
  }
}
```

以下の場合、idがuuidのフォーマットじゃないよとエラーになります。

```json
{
  "app": {
    "id": 32929,
    "created_at": "2012-01-01T12:00:00Z",
    "updated_at": "2012-01-01T12:00:00Z"
  }
}
```

つまり、prmdを使ってschema.jsonを作った場合,`properties`を調整する必要があります。

## propertiesを調整する

propertiesを上のものに合わせる場合、
以下のような方法が考えられると思います。

1. 仕様を合わせる
2. combine時に使うmeta.jsonに設定をする
3. schema.jsonを動的に修正する

1.は仕様を上のようなリソース名をレスポンスに含めるというもの。
新規ではじめるならば、アリかもしれません。

2.は、`combine`時の`meta`オプションで指定するmeta.jsonに
propertiesを入れます。

たとえば、[instagramのAPI](http://instagram.com/developer/restrict-api-requests/)のフォーマットである`{"meta":{"code":200},"data":null}`にしたい場合

```json:meta.json
{
  "properties" : {
    "meta": {
      "$ref": "#/definitions/meta"
    },
    "data": {
      "type": "object",
      "anyOf": [
        {"$ref": "#/definitions/user"},
        {"$ref": "#/definitions/media"}
      ]
    }
  }
}
```
とフォーマットを合わせたりします。
(propertiesは上書きではなく、マージなので不必要なプロパティが残るので、なんらかの対応する必要があります。

3.は例えば上の場合、`anyOf`がどれか１つ以上のフォーマットにマッチしていればOKというものなので、厳密に行うにはバリデーションチェックの前に`anyOf`の内容を書き換えたりするというものです。

どれも最適解ではない気がしますが、
ここら辺をいじると各APIのリソースのバリデーションが出来るようになります。

ただ、これでもlinkの違いを自動的に判別してテストはしてくれないので
それなりのコードは書くことになります。

# まとめ

なんかすごい長めにややこしいこと書いてましたが、
時系列には、こんなことになってました。

1. API Blueprintだと、スペルミスとか自動的にチェックしきれなくてつらい　
2. JSON Schemaからだと、バリデーションのチェックが出来そう
3. 細かいリクエストのスキーマの調整が面倒すぎるし、linksに対応したバリデーションはある程度自分で書かなければいけなさそう

いまだに、ベストな方法が調べきれていないのですが
なんか当たり前な方法があったりするような気がしないでもなく、
みんなどうやってるんだろ、って感じです。

# 参考

* [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/index.html)
* [JSON Schema で Web API のスキマを埋めよう](https://speakerdeck.com/co3k/json-schema-de-web-api-falsesukimawomai-meyou)
* [The Heroku HTTP API Toolchain](https://blog.heroku.com/archives/2014/5/20/heroku-http-api-toolchain#schema-and-documentation-toolchain)

# 関連

* [APIドキュメントを支える技術](http://qiita.com/taizo/items/5a969ace57394a2d5b4a?utm_source=link&utm_medium=href&utm_campaign=link)