JSONでフォームを表現する


先日るびまに寄稿した[ハイパーメディア：RailsでWeb APIをつくるには、これが足りない](http://magazine.rubyist.net/?0050-Hypermedia)ではWeb APIにリンクとフォームが必要だということを強調しました。幸いリンクとしてURLが入ったWeb APIは少しずつ増えてきているように思います。

しかし、リンクでは決まったGETリクエストしかできません。ユーザから入力されたパラメータを付加したGETやPOSTリクエストを行うためにはフォームが必要です。

そこで、フォームをJSONベースのフォーマットで表現するとどうなるか、数少ない現時点での方法を調べました。

* [JSON-LD](http://json-ld.org/) & [Schema.org](http://schema.org/)
* JSON-LD & [Hydra](http://www.hydra-cg.com/)
* [UBER](http://uberhypermedia.org/)


# 1. 検索フォーム(GET)

## HTML


```html
<form action="http://example.com/search" method="get">
  <input type="search" name="q" maxlength="100" required>
  <input type="submit">
</form>
```

```
GET http://example.com/search?q=the+search
```

<!--
### 参考：microdata Schema.orgでマークアップ

```html
<div itemscope itemtype="http://schema.org/Thing">
  <form itemprop="potentialAction" itemscope itemtype="http://schema.org/SearchAction"  action="http://example.com/search" method="get">
    <meta itemprop="target" content="http://example.com/search?q={q}">
    <input itemprop="query-input" type="search" name="q" maxlength="100" required>
    <input type="submit">
  </form>
</div>
```

[Googleによると](https://developers.google.com/structured-data/slsb-overview)query-inputは`<input>`につければいいらしい。わかりやすい。
-->

## JSON-LD & Schema.org


```json
{
  "@context": "http://schema.org/",
  "@type": "Thing",
  "potentialAction": {
    "@type": "SearchAction",
    "target": "http://example.com/search?q={q}",
    "query-input": "name=q maxlength=100 required"
  }
}
```

Schema.orgではフォームをActionで表現する。アクション元のタイプ`Thing`はSchema.orgのタイプなら何でもよい。
アクションタイプ`SearchAction`もどんな[Action](http://schema.org/Action)でもOK。
query-inputは以下のオブジェクト表現も可

```json
"query-input": {
  "@type": "PropertyValueSpecification",
  "valueName": "q",
  "valueMaxlength": 100,
  "valueRequired": true
}
```

Google Sitelinks Search Boxでもこれを採用している。
https://developers.google.com/structured-data/slsb-overview

## JSON-LD & Hydra

```json
{
  "@context": "http://www.w3.org/ns/hydra/core",
  "@type": "Collection",
  "search": {
    "@type": "IriTemplate",
    "template": "http://example.com/search?q={q}",
    "variableRepresentation": "BasicRepresentation",
    "mapping": [
      {
        "@type": "IriTemplateMapping",
        "variable": "q",
        "property": "hydra:freetextQuery",
        "required": true
      }
    ]
  }
}    
```

HydraのCollectionタイプに対してsearchを記述できる。
検索以外のGETフォームの書き方はわからない。

## UBER

```json
{
  "uber": {
    "version": "1.0",
    "data": [
      {
        "name": "search",
        "rel": "search",
        "url": "http://example.com/search?q={q}",
        "templated": "true",
        "action": "read"
      }
    ]
  }
}
```

`"templated": "true"`にすると`url`がURI Templateとみなされる。
`"action": "read"`がGETリクエストを表す。
値の制約などを表すことはできない（仕様に提案されている段階）。

# 2. レビュー投稿フォーム(POST)

## HTML

```html
<form action="http://example.com/movies/123/reviews" method="post">
  <input type="text" name="body" maxlength="100" required>
  <input type="number" name="rating" min="1" max="5" required>
  <input type="submit">
</form>
```
```
POST http://example.com/movies/123/reviews

body=yada%2C%20yada%2C%20yada&rating=4
```

<!--
### 参考：microdata Schema.orgでマークアップ

```html
<div itemscope itemtype="http://schema.org/Thing">
  <form itemprop="potentialAction" itemscope itemtype="http://schema.org/ReviewAction"  action="http://example.com/movies/123/reviews" method="post">
    <meta itemprop="target" content="http://example.com/movies/123/reviews">
    <div itemprop="result" itemscope itemtype="http://schema.org/Review">
      <input itemprop="reviewBody-input" type="text" name="body" maxlength="100" required>
      <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
        <input itemprop="ratingValue-input" type="number" name="rating" min="1" max="5" required>
      </div>
    </div>
    <input type="submit">
  </form>
</div>
```
-->

## JSON-LD & Schema.org

```json
{
  "@context": "http://schema.org/",
  "@type": "Thing",
  "potentialAction": {
    "@type": "ReviewAction",
    "target": {
      "@type": "EntryPoint",
      "urlTemplate": "http://example.com/movies/123/reviews",
      "encodingType": "application/x-www-form-urlencoded",
      "contentType": "application/ld+json",
      "httpMethod": "POST"
    },
    "object" : "http://example.com/movies/123",
    "result": {
      "@type": "Review",
      "reviewBody-input": "name=body maxlength=100 required",
      "reviewRating": {
        "ratingValue-input": "name=rating min=1 max=5 required"
      }
    }
  }
}
```

encodingTypeはリクエストのメディアタイプ、contentTypeがレスポンスのメディアタイプ。
encodingTypeがapplication/x-www-form-urlencodedの場合は明確に定義されていないが、検索の場合から考えてこれで妥当と思われる。
encodingTypeがapplication/ld+jsonの場合は、*-inputのプロパティを含めたActionのJSON-LDをリクエストボディとする。このようなリクエストになる。

```
POST http://example.com/movies/123/reviews

{
  "@context": "http://schema.org/",
  "@type": "ReviewAction",
  "result": {
    "@type": "Review",
    "reviewBody": "yada, yada, yada",
    "reviewRating": {
      "ratingValue": "4"
    }
  }
}
```

## JSON-LD & Schema.org (Google Email Markup拡張)

```json
{
  "@context": "http://schema.org/",
  "@type": "EmailMessage",
  "potentialAction": {
    "@type": "ReviewAction",
    "resultReview": {
      "@type": "Review",
      "reviewRating": {
        "@type": "Rating",
        "bestRating": "5",
        "worstRating": "1"
      }
    },
    "handler": {
      "@type": "HttpActionHandler",
      "url": "http://example.com/movies/123/reviews",
      "requiredProperty": [
        {
          "@type": "Property",
          "name": "resultReview.reviewBody"
        },
        {
          "@type": "Property",
          "name": "resultReview.reviewRating.ratingValue"
        }
      ],
      "method": "http://schema.org/HttpRequestMethod/POST"
    }
  }
}
```
Email Markup用拡張。独自にhandler(HttpActionHandler)を追加している。
https://developers.google.com/schemas/reference/types/HttpActionHandler


## JSON-LD & Hydra

### 拡張形 (augmented operation)

```json
{
  "@context": "http://www.w3.org/ns/hydra/core",
  "@id": "http://example.com/movies/123/reviews",
  "@type": "Resource",
  "operation": [
    {
      "@type": "CreateResourceOperation",
      "method": "POST",
      "expects": {
        "supportedProperty": [
          {
            "property": "body",
            "range": "xsd:string",
            "required": true
          },
          {
            "property": "rating",
            "range": "xsd:integer",
            "required": true
          }
        ]
      },
      "returns": "Resource"
    }
  ]
}
```

Resourceタイプに対してoperationを記述する形。ただし対象URLはResource自身(`@id`)のみ。
[HydraConsole](http://www.markus-lanthaler.com/hydra/console/)では認識されない？


### 定義形 (supported operation)

```json
{
  "@context": "http://www.w3.org/ns/hydra/core",
  "@id": "http://example.com/doc#postReview",
  "@type": "Link",
  "supportedOperation": [
    {
      "@type": "CreateResourceOperation",
      "method": "POST",
      "expects": "http://example.com/doc#Review",
      "returns": "http://example.com/doc#Review"
    }
  ]
}
```

ClassもしくはLinkタイプに対してsupportedOperationとして定義する形。この定義をApiDocumentationとして用意しておき、`"http://example.com/doc#postReview": "http://example.com/movies/123/reviews"`のように使うと有効になる。
データやcontextとは別のドキュメントとして定義するため、クライアントの実装が少し複雑になる。


## UBER

```json
{
  "uber": {
    "version": "1.0",
    "data": [
      {
        "name": "create",
        "url": "http://example.com/movies/123/reviews",
        "model": "body={body}&rating={rating}",
        "action": "append"
      }
    ]
  }
}
```
`model`がリクエストボディのテンプレートになる。
`"action": "append"`がPOSTリクエストを表す。
値の条件などを表すことはできない。


---

# 参考文献
http://schema.org/docs/actions.html
https://developers.google.com/structured-data/
https://developers.google.com/gmail/markup/
https://developers.google.com/schemas/
http://www.hydra-cg.com/spec/latest/core/
http://rawgit.com/uber-hypermedia/specification/master/uber-hypermedia.html