2014/11/11

JSONスキーマからGo言語コードを作成して利用してみる

JSONスキーマにサーバ側APIについての記述を書けたり、ソースコードが生成できたりするので、いろいろ試してみました。

今回行ったことは次の通りです。

  • IIJmioクーポンスイッチAPIのドキュメントを参考に、JSONスキーマを勝手に書いた
  • JSONスキーマ用のツールprmdでAPIドキュメントを作成したり、スキーマの検証を行った
  • JSONスキーマ用からGo言語のクライアントコードを生成するツールschematicでコードを生成して、それを使った

prmdを導入する

とりあえず、JSONスキーマ用のツールprmdを導入しておきます。gemで一発です。

$ sudo gem install prmd

JSONスキーマを記述する

IIJmioクーポンスイッチAPIを参考に、JSONスキーマを書いてみました。

prmdに雛形を作成する機能があるので、それを利用して書いていきます。

$ mkdir schemata
$ prmd init base > schemata/base.json

3つのAPIしかないとは言え、JSONスキーマの仕様について調べながらなので書くのに時間がかかりました。

なお、prmdでのスキーマを書く作法的には、メンテナンスしやすいように、APIを機能ごとファイルを分けるように書くとよいみたいです。

基本的には、definitionsあたりにデータやパラメータの定義を書いておき、propertiesにAPIの戻り値オブジェクトの定義を書き、linksにそれが返されるAPIの定義を書く、という感じみたいです。

"$ref": "/foo/bar#/hoge/fuga"みたいなのはJSON Pointerというもので、 この場合は、idが/foo/barなオブジェクトの["hoge"]["fuga"]なオブジェクトを参照するということを意味しています。

ちなみに、既存のサービスとして、Google APIHeroku Platform APIではAPIにJSONスキーマが用意されているので、それを見て参考にしたりしました。

これらは、例えば次のようにしてJSONスキーマを取得できます (Googleのはブラウザで直接見ることもできます)。

curl https://api.heroku.com/schema -H "Accept: application/vnd.heroku+json; version=3"
curl -O https://www.googleapis.com/discovery/v1/apis

prmdでAPIドキュメントを作成する

JSONスキーマを書き終えたら、まずprmdで分割したファイルを結合して、ひとつのスキーマにします。

$ mkdir gen
$ prmd combine --meta meta/meta.json schemata > gen/schema.json

なお、meta.jsonは次のようなファイルです。

$ cat meta/meta.json
{
    "description": "IIJmio Coupon API",
    "id": "iijmio",
    "links": [{
        "href": "https://api.iijmio.jp/mobile/d/v1",
        "rel": "self"
    }],
    "title": "IIJmio"
}

続いて、作成されたスキーマが正しいか検証してみます。

$ prmd verify gen/schema.json

エラー報告がなければドキュメントを生成してみましょう。

$ prmd doc gen/schema.json > gen/schema.md

これで生成されたドキュメントはこちら

Attributesのあたりがやたら見づらいですが、それっぽい感じのドキュメントになっています。

schematicでGoのクライアントコードを生成する

最後に、Go言語のJSONスキーマ用ツールschematicでクライアントコードの雛形をつくって、それを利用してみましょう。

まず、schematicをてきとうに導入します。

$ git clone  https://github.com/interagent/schematic.git
$ cd schematic
$ go build cmd/schematic/schematic.go

schematicの使いかたは、schematic foo.json > foo.goでよいのですが、日本語コードが入っているとエラーになるようなので、次のようにてきとうに取り除きました。

$ mkdir -p src/iijmio
$ schematic =(sed -E 's/"description":.+//' gen/schema.json) > src/iijmio/iijmio.go

そして、生成されたコードがそのままでは利用できないので、を次のように修正しました。

  • timeパッケージがなぜかimportされているが、不要なのでコメントアウトした
  • Versionが空文字なので入れておいた
  • req.HeaderX-IIJmio-DeveloperX-IIJmio-Authorizationを入れておいた
  • コードではGetがトップレベルがリストであるデータ取得を想定しているが、クーポンAPIではそうではないので、リストを取らないようにした

具体的な修正内容はこのコミットを見てください。

あとは、これを利用するメインコードを書いて終わりです。

package main

import (
    "iijmio"
    "fmt"
)

func main() {
  mio := iijmio.NewService(nil)

  cs, err := mio.CouponList(nil)
  fmt.Println(cs, err)

  ps, err := mio.PacketList(nil)
  fmt.Println(ps, err)
}

これを実行すると次のような感じで出力されます (実際にはインデントや改行は入りません)。

&{[
   {[{0xc208249140 bundle 1420}
     {0xc208249160 bundle 2000}
     {0xc208249180 topup 0}
     {0xc2082491a0 topup 0}
     {0xc2082491c0 topup 0}
     {0xc2082491e0 topup 0}]
     hddzzzzzzzz
     [{[{<nil> sim 1}] true
       hdoxxxxxxxx GDxxxxxxxxxxxx yyyyyyyyyyy
       false false false}] Minimum Start}
   {[{0xc208249330 bundle 120}
     {0xc208249350 bundle 2000}
     {0xc208249370 topup 0}
     {0xc2082493a0 topup 0}
     {0xc2082493c0 topup 0}
     {0xc2082493e0 topup 0}]
     hddxxxxxxxy
     [{[{<nil> sim 6}] true
       hdoxxxxxxxy DNxxxxxxxxxxxxy yyyyyyyyyyz
       false true true}] Minimum Start}]
 OK} <nil>

&{[
{hddxxxxxxxx
  [{hdoxxxxxxxx
    [{20141006 33 0}
     {20141007 64 0}
     {20141008 60 15}
           :
    ]}] Minimum Start}
{hddxxxxxxxy
  [{hdoxxxxxxxxy
    [{20141006 0 0}
     {20141007 0 0}
     {20141008 0 0}
   ]}] Minimum Start}
] OK} <nil>

ちなみに、クーポン利用状態変更については、できるかどうか確認していません。というか、メソッドCouponChangeを呼ぶためのコードが私には書けませんでした。

func (s *Service) CouponChange(o struct {
    CouponInfo []struct {
        HdoInfo []struct {
            CouponUse      bool   `json:"couponUse"`
            HdoServiceCode string `json:"hdoServiceCode"`
        } `json:"hdoInfo"`
    } `json:"couponInfo"`
}) (*Coupon, error) {
    var coupon Coupon
    return &coupon, s.Put(&coupon, fmt.Sprintf("/coupon/"), o)
}

おわりに

JSONスキーマをつくって、そこからGo言語コードを自動生成してみました。

JSONオブジェクトを定義するだけではなく、REST APIをより詳しく定義したい場合にはSwaggerというものもあるみたいです。swaggerの仕様にはREST API向けの属性がいろいろ定義されているので便利そうです。

また、今回はJSONスキーマを手書きで書きましたが、ソースコードから自動生成するというアプローチもあるみたいです。例えば、Go言語のbeego Webフレームワークを利用していたら、コードのコメントからSwaggerのスペックファイルを生成したりできるようです。

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。