prmd is 何?
prmd とは、Heroku が提供している OSS で、JSON Schema を簡単に取り扱えるようにする gem です。prmd を利用することで、API のリソース単位で JSON Schema ファイルを管理しつつ、あとで結合して一つの JSON Schema ファイルにしたり、JSON Schema ファイルに間違いがないかどうかを検証したり、JSON Schema から Markdown 形式の仕様書を自動生成したり、といったことが簡単に出来るようになります。
まずは、インストールしてみよう!
Rails で利用することを勝手に想定しているので、Gemfile
に追記して bundle install
するだけで入ります。
gem 'prmd', github: 'interagent/prmd'
$ bundle install
JSON Schema ってどこにどうやって置いて管理したらいいの?
prmd の README にある File Layout の項で、JSON Schema のオススメのファイル構成について言及されていたので、積極的に真似していくスタイルがヨサソウです。
/docs # top-level directory for project documentation
/schema # API schema documentation
/schemata
/{resource.[json,yml]} # individual resource schema
/meta.[json,yml] # overall API metadata
/overview.md # preamble for generated API docs
/schema.json # complete generated JSON schema file
/schema.md # complete generated API documentation file
それぞれのファイルについて、簡単に解説しておきます。
- /docs/schema/schemata/{resource.[json,yml]}
- API のリソース単位で用意する JSON Schema ファイル
- prmd を利用することで JSON 形式だけでなく YAML 形式で書くことも出来る
- /docs/schema/meta.[json,yml]
- API の全体に関わるメタデータを記述するファイル
- prmd を利用することで JSON 形式だけでなく YAML 形式で書くことも出来る
- /docs/schema/overview.md
- 仕様書のイントロダクションを記述する Markdown ファイル
- 仕様書を自動生成するときにこのファイルを指定することによって、仕様書の先頭に結合することが出来る
- /docs/schema/schema.json
- API の全てのリソースの JSON Schema が結合された全部入りの JSON Schema ファイル
- 仕様書を自動生成するときの元になる
- 別途、committee などの gem を入れることで、リクエストやレスポンスのバリデーションにも利用される
- /docs/schema/schema.md
- prmd を利用して JSON Schema から自動生成した Markdown 形式の仕様書
JSON Schema の雛形を自動生成しよう
JSON Schema をゼロから書いていくのは、とても心が折れる作業なので、スッと雛形を自動生成してから変更していくスタイルがヨサソウですね。
# JSON 形式で自動生成する
$ prmd init product > docs/schema/schemata/product.json
# YAML 形式で自動生成する
$ prmd init --yaml product > docs/schema/schemata/product.yml
ちなみに、既に、リソースが存在しているかどうかは関係無く、予め、prmd で用意されている雛形から自動生成されるようになっていて、以下のような、JSON Schema の雛形が自動生成されます。
---
"$schema": http://json-schema.org/draft-04/hyper-schema
title: FIXME - Product
description: FIXME
stability: prototype
strictProperties: true
type:
- object
definitions:
id:
description: unique identifier of product
readOnly: true
format: uuid
type:
- string
name:
description: unique name of product
readOnly: true
type:
- string
identity:
anyOf:
- "$ref": "/schemata/product#/definitions/id"
- "$ref": "/schemata/product#/definitions/name"
created_at:
description: when product was created
format: date-time
type:
- string
updated_at:
description: when product was updated
format: date-time
type:
- string
links:
- description: Create a new product.
href: "/products"
method: POST
rel: create
schema:
properties: {}
type:
- object
title: Create
- description: Delete an existing product.
href: "/products/{(%2Fschemata%2Fproduct%23%2Fdefinitions%2Fidentity)}"
method: DELETE
rel: destroy
title: Delete
- description: Info for existing product.
href: "/products/{(%2Fschemata%2Fproduct%23%2Fdefinitions%2Fidentity)}"
method: GET
rel: self
title: Info
- description: List existing products.
href: "/products"
method: GET
rel: instances
title: List
- description: Update an existing product.
href: "/products/{(%2Fschemata%2Fproduct%23%2Fdefinitions%2Fidentity)}"
method: PATCH
rel: update
schema:
properties: {}
type:
- object
title: Update
properties:
created_at:
"$ref": "/schemata/product#/definitions/created_at"
id:
"$ref": "/schemata/product#/definitions/id"
name:
"$ref": "/schemata/product#/definitions/name"
updated_at:
"$ref": "/schemata/product#/definitions/updated_at"
id: schemata/product
複数の JSON Schema を結合しよう
API のリソース単位で JSON Schema を用意したとしても、JSON Schema を利用してリクエストやレスポンスのバリデーションを行ったり、仕様書を自動生成するためには、最終的に、一つの JSON Schema に結合してあげる必要があります。
指定ディレクトリ配下にある JSON Schema を結合する
$ prmd combine docs/schema/schemata/ > docs/schema/schema.json
指定ディレクトリ配下にある JSON Schema と、指定した メタファイルを結合する
# メタファイルを JSON 形式で指定する場合
$ prmd combine --meta docs/schema/meta.json docs/schema/schemata/ > docs/schema/schema.json
# メタファイルを YAML 形式で指定する場合
$ prmd combine --meta docs/schema/meta.yml docs/schema/schemata/ > docs/schema/schema.json
例えば、以下のようなメタファイルがあったとします。
---
"$schema": http://json-schema.org/draft-04/hyper-schema
title: Sample API JSON Schema
description: In this schema file, we represents the public interface of Sample API in JSON Hyper Schema draft v4.
id: sample
links:
- href: https://example.com
rel: self
先ほど、雛形として自動生成した JSON Schema と、上記のメタファイルを結合すると、以下のような JSON Schema が出来上がります。
{
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"type": [
"object"
],
"definitions": {
"product": {
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"title": "FIXME - Product",
"description": "FIXME",
"stability": "prototype",
"strictProperties": true,
"type": [
"object"
],
"definitions": {
"id": {
"description": "unique identifier of product",
"readOnly": true,
"format": "uuid",
"type": [
"string"
]
},
"name": {
"description": "unique name of product",
"readOnly": true,
"type": [
"string"
]
},
"identity": {
"anyOf": [
{
"$ref": "#/definitions/product/definitions/id"
},
{
"$ref": "#/definitions/product/definitions/name"
}
]
},
"created_at": {
"description": "when product was created",
"format": "date-time",
"type": [
"string"
]
},
"updated_at": {
"description": "when product was updated",
"format": "date-time",
"type": [
"string"
]
}
},
"links": [
{
"description": "Create a new product.",
"href": "/products",
"method": "POST",
"rel": "create",
"schema": {
"properties": {
},
"type": [
"object"
]
},
"title": "Create"
},
{
"description": "Delete an existing product.",
"href": "/products/{(%23%2Fdefinitions%2Fproduct%2Fdefinitions%2Fidentity)}",
"method": "DELETE",
"rel": "destroy",
"title": "Delete"
},
{
"description": "Info for existing product.",
"href": "/products/{(%23%2Fdefinitions%2Fproduct%2Fdefinitions%2Fidentity)}",
"method": "GET",
"rel": "self",
"title": "Info"
},
{
"description": "List existing products.",
"href": "/products",
"method": "GET",
"rel": "instances",
"title": "List"
},
{
"description": "Update an existing product.",
"href": "/products/{(%23%2Fdefinitions%2Fproduct%2Fdefinitions%2Fidentity)}",
"method": "PATCH",
"rel": "update",
"schema": {
"properties": {
},
"type": [
"object"
]
},
"title": "Update"
}
],
"properties": {
"created_at": {
"$ref": "#/definitions/product/definitions/created_at"
},
"id": {
"$ref": "#/definitions/product/definitions/id"
},
"name": {
"$ref": "#/definitions/product/definitions/name"
},
"updated_at": {
"$ref": "#/definitions/product/definitions/updated_at"
}
}
}
},
"properties": {
"product": {
"$ref": "#/definitions/product"
}
},
"title": "Sample API JSON Schema",
"description": "In this schema file, we represents the public interface of Sample API in JSON Hyper Schema draft v4.",
"id": "sample",
"links": [
{
"href": "https://example.com",
"rel": "self"
}
]
}
メタファイルも一緒に結合されていますね。
JSON Schema に間違いがないか検証したい
頑張って書き上げた JSON Schema に間違いがないかどうか、検証したいときに使いましょう。
$ prmd verify docs/schema/schema.json
例えば、プロパティで参照している定義が見つからない場合は、以下のようなエラーが返って来ます。
schema.json: #: Couldn't resolve pointer "#/definitions/product/definitions/name".
schema.json: #: Couldn't resolve references: #/definitions/product/definitions/name, #/definitions/product/definitions/name.
JSON Schema から仕様書を自動生成したい
JSON Schema さえあれば、Markdown 形式の仕様書を簡単に自動生成することが出来ます。
指定した JSON Schema から仕様書を自動生成する
$ prmd doc docs/schema/schema.json > docs/schema/schema.md
指定した JSON Schema の先頭に、指定した Markdown ファイルを結合して仕様書を自動生成する
API に関する概要などを仕様書の先頭に書きたい場合に使います。
$ prmd doc --prepend docs/schema/overview.md docs/schema/schema.json > docs/schema/schema.md
例えば、以下のような Markdown ファイルがあったとします。
# Sample API JSON Schema
In this schema file, we represents the public interface of Sample API in JSON Hyper Schema draft v4.
先ほど、結合した JSON Schema と、上記の Markdown ファイルを指定して仕様書を自動生成すると、以下のような Markdown 形式の仕様書が出来上がります。
# Sample API JSON Schema
In this schema file, we represents the public interface of Sample API in JSON Hyper Schema draft v4.
## Product
Stability: `prototype`
FIXME
### Attributes
| Name | Type | Description | Example |
| ------- | ------- | ------- | ------- |
| **created_at** | *date-time* | when product was created | `"2015-01-01T12:00:00Z"` |
| **id** | *uuid* | unique identifier of product | `"01234567-89ab-cdef-0123-456789abcdef"` |
| **name** | *string* | unique name of product | `"example"` |
| **updated_at** | *date-time* | when product was updated | `"2015-01-01T12:00:00Z"` |
### Product Create
Create a new product.
```
POST /products
```
#### Curl Example
```bash
$ curl -n -X POST https://example.com/products \
-d '{
}' \
-H "Content-Type: application/json"
```
#### Response Example
```
HTTP/1.1 201 Created
```
```json
{
"created_at": "2015-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example",
"updated_at": "2015-01-01T12:00:00Z"
}
```
### Product Delete
Delete an existing product.
```
DELETE /products/{product_id_or_name}
```
#### Curl Example
```bash
$ curl -n -X DELETE https://example.com/products/$PRODUCT_ID_OR_NAME \
-H "Content-Type: application/json"
```
#### Response Example
```
HTTP/1.1 200 OK
```
```json
{
"created_at": "2015-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example",
"updated_at": "2015-01-01T12:00:00Z"
}
```
### Product Info
Info for existing product.
```
GET /products/{product_id_or_name}
```
#### Curl Example
```bash
$ curl -n https://example.com/products/$PRODUCT_ID_OR_NAME
```
#### Response Example
```
HTTP/1.1 200 OK
```
```json
{
"created_at": "2015-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example",
"updated_at": "2015-01-01T12:00:00Z"
}
```
### Product List
List existing products.
```
GET /products
```
#### Curl Example
```bash
$ curl -n https://example.com/products
```
#### Response Example
```
HTTP/1.1 200 OK
```
```json
[
{
"created_at": "2015-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example",
"updated_at": "2015-01-01T12:00:00Z"
}
]
```
### Product Update
Update an existing product.
```
PATCH /products/{product_id_or_name}
```
#### Curl Example
```bash
$ curl -n -X PATCH https://example.com/products/$PRODUCT_ID_OR_NAME \
-d '{
}' \
-H "Content-Type: application/json"
```
#### Response Example
```
HTTP/1.1 200 OK
```
```json
{
"created_at": "2015-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example",
"updated_at": "2015-01-01T12:00:00Z"
}
```
仕様書の先頭に、指定した Markdown ファイルが結合されていますね。
仕様書としては、それぞれのリソースに対して、タイトル、説明、エンドポイント、コピペして直接叩ける Curl のサンプルとレスポンスのサンプルが記載されます。
Rake タスクを用意して手抜きしよう
毎回、細かいファイルやオプションを指定するのも面倒なので、README の Use as rake task の項にも記載されているように、Rake タスクを用意して手抜きするとヨサソウです。
require 'prmd/rake_tasks/combine'
require 'prmd/rake_tasks/verify'
require 'prmd/rake_tasks/doc'
schema_root = "docs/schema"
namespace :schema do
Prmd::RakeTasks::Combine.new do |t|
t.options[:meta] = "#{schema_root}/meta.yml"
t.paths << "#{schema_root}/schemata"
t.output_file = "#{schema_root}/schema.json"
end
Prmd::RakeTasks::Verify.new do |t|
t.files << "#{schema_root}/schema.json"
end
Prmd::RakeTasks::Doc.new do |t|
t.options[:prepend] = ["#{schema_root}/overview.md"]
t.files = { "#{schema_root}/schema.json" => "#{schema_root}/schema.md" }
end
end
desc 'Combine & Verify schema & Generate documentation'
task schema: ['schema:combine', 'schema:verify', 'schema:doc']
# 結合したいとき
$ rake schema:combine
# 検証したいとき
$ rake schema:verify
# 仕様書を自動生成したいとき
$ rake schema:doc
# 上記全てを実行したいとき
$ rake schema
リンク
合わせて読みたい
- HerokuのつくってるAPI関係の便利なやつ - ✘╹◡╹✘
- JSON Schemaを上手く運用出来そうなprmdとその周りのお話 - Qiita
- JSON Hyper-Schema からAPIドキュメントとGoのコードを自動生成する | The Wacul Blog
- HerokuのAPIデザイン | SOTA
- JSON SchemaでAPI開発を自動化する — Commerce Hack
- Safx: JSONスキーマからGo言語コードを作成して利用してみる
- 現実世界のJSON Hyper-Schema / Real World JSON Hyper-Schema - SSSSLIDE
- Qiita API v2のJSON Schemaを公開しました - Qiita Blog