こういう名前は正しくないかもしれないが,ここでは REST API パッケージ(ライブラリ)とは,既存の REST API を走査するための Haskell パッケージのことを指してる. 例えば,既にあるものだと,GitHub APIgithubSlack APIslack-api などがある.

とある事情で,ChatWork API の Haskell パッケージを req ライブラリを使って作ったので,その過程を残しておく.

前回で,基本的なエンドポイント関数は作れた. 今回は,エラー用の JSON が返ってきたときの処理の追加と,(自分流の)テストの追加を書こうと思う.

ちなみに,完成品はココにある.

作る

エラー用の JSON への処理の追加

例えば前回に次のようなエンドポイント関数を定義した(今回は詳細を割愛).

次のように用いる.

token という変数は,名前の通り発行した認証トークンを束縛している. API の定義では,ここで間違ったトークンを与えると,次のような JSON を返すということになっている

現状の getMe 関数は,Me 型に対応する JSON しかパースできず,この形の JSON はパースエラーとなる(そりゃそう). なのでうまい事 Either 型なんかを使ってラップしてやる必要がある.

まずは,このエラーの場合の JSON 用の型を定義する.

これと何らかの型を Either 型でラップしてあげる.

ついでに,JsonResponse のラップした. こうしておくと,例えば JsonResponse Me と書いてた部分を ChatWorkResponse Me と置き換えるだけで良くなる.

Either ChatWorkErrors a 型を FromJSON 型クラスのインスタンスにするには少しだけ工夫が要る. なぜなら,すでに Either e a 型がインスタンスになっているから. そのために FlexibleInstances 言語拡張をして,{-# OVERLAPS #-} を書き加える必要がある.

ちなみに,Either e a 型のインスタンスではダメで,これは Left とか Right とかを含んだ文字列じゃないとパースできない.

(自分流の)テストの追加

で最後にテストを追加しようと思う. TDD的には最悪なのは分かるが,全部手探りで進めたので許してほしい.

理想としては,hspec パッケージを使って次のように書きたい.

しかし,認証トークンを直接書きたくないし,そもそもChatWork API のサーバーに直接通信したくない(サーバーに問題があってもテストエラーになってしまうから). そのために認証の要らないモックサーバーを立てよう.

イロイロ調べた結果 hspecaround 関数や around_ 関数を利用すると,テストを実行する際に任意の IO アクションを実行できるようだ.

モックサーバーは servant-server パッケージを使って立てる. servant-server の使い方は細かくは解説しない(ググるなり,ぼくのコードを見るなりしてください).

空文字が返ってきたら [] にする処理を,ヘッダーの Content-Length を見て処理しているので,Headers 型に '[Header "Content-Length" Int64] を与えて,addHeader 関数を使ってヘッダーに書き加えている.

あとは mockServer 関数を IO () -> IO () 型になるようにラップするだけ. ここで,普通に mockServer 関数を実行するとプログラムがそこで止まってしまう. なので,forkIO 関数を使って子プロセスで実行する.

引数の action は(おそらく)実行する Spec 型のテストセットだと思う. プロセスは,テストが終わってから殺してほしいので,finally 関数を使って,そのように指定する.

runMockServer 関数を使う前に,認証トークンと baseUrl を隠蔽して抽象化してくれる,Client 型クラスを定義しておく.

Client 型クラスを用いると,今までのエンドポイント関数は次のような型に書き換わる.

ちなみに,普通に ChatWork API サーバーとやり取りする場合には次のような型を定義して用いる.

そして,テストの場合は次のようになる.

そしていよいよ,runMockServer 関数を使って Spec 型を構成する.

これらを 全てのエンドポイント分 作る…(苦行).

ChatWork.MonadHttpIO とは?

ちなみに,途中でインポートした ChatWork.MonadHttpIO モジュールは何かというと,ただの MonadHttp 型クラスの IO 型のインスタンスである.

なんでこんなことをしているかと言うと,req 関数(や req' 関数)を利用するには,MonadHttp 型クラスのインスタンスの中でないといけないからだ. つまり,これが無いと IO 型である main 関数の中や ghci で利用できないのだ.

実はこの問題req-4.0 では既に解決済みで,Req 型を使えばよくなっている. こんな感じに実行できる.

おしまい

このあと Haskage や Stackage に登録して,無事作ったライブラリが Nightly に登録された. 次回はその過程も書いてみようかなぁ(ググればわかるんだけど).