手探りで haskell から https 通信をして github api を呼んでみた（多分これが一番簡単だと思います）

haskell はある程度触っているけど http 通信をしたことはなかった。
なんかライブラリがいくつもあってどれを使ったら良いか分からないってのと、普段 http 通信は python とかでやっちゃうから避けてた。

そんな haskell の http まわり素人が２〜３日くらいで手探りしたのをまとめておく。
（なのでかなり簡単な内容だと思う）

あとあんまパラメータありで post をしている記事を見かけなかったから、ってのもある。

## とりあえず参照
まずはこれだけを目指す

```
$ curl https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones

[
  {
    "number": 2,
    "title": "sprint 2"
  },
  {
    "number": 1,
    "title": "sprint 1"
  }
]
# 出力はごっそりカット
```

### `Request`を作る
どうも色々調べると、`parseRequest`ってので始まってるコードが大半なので、それを真似してみる
[http-conduit](https://hackage.haskell.org/package/http-conduit-2.3.1/docs/Network-HTTP-Conduit.html) を依存に追加する

```haskell
> import Network.HTTP.Conduit
```

#### `String`から`Request`を作れる
`m Request`で返ってくるのと`Request`で返ってくるのがある

```haskell
> :t parseRequest
exceptions-0.8.3:Control.Monad.Catch.MonadThrow m => String -> m Request
```
     
```haskell
> :t parseRequest_
String -> Request
```

とりあえず`Request`を手に入れてみるとこんな感じ
`port`とか`secure`とかをよしなにしてくれる

```haskell
> request = parseRequest_ "https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones"

> print request
Request {
  host                 = "api.github.com"
  port                 = 443
  secure               = True
  requestHeaders       = []
  path                 = "/repos/suzuki-hoge/has-hub-workspace/milestones"
  queryString          = ""
  method               = "GET"
  proxy                = Nothing
  rawBody              = False
  redirectCount        = 10
  responseTimeout      = ResponseTimeoutDefault
  requestVersion       = HTTP/1.1
}
```

不正な文字列だと失敗する
`m`に応じて`Exception`だったり`Nothing`だったりする

```haskell
> parseRequest "localhost" :: IO Request
*** Exception: InvalidUrlException "localhost" "Invalid URL"
```

```haskell
> parseRequest "localhost" :: Maybe Request
Nothing
```

今回はとりあえず`parseRequest`でも`parseRequest_`でもどっちでも良い

### `Manager`を作る
どうももう一つ`manager`というのをよく見るので、それもまねてみる
依存に[http-client](https://hackage.haskell.org/package/http-client-0.5.12/docs/Network-HTTP-Client.html)を足す

```haskell
> import Network.HTTP.Client
```

#### 何やらとりあえずこれ使っとけ的なのがある
```haskell
> manager <- newManager defaultManagerSettings
```

### 投げる
怒られた

```
> httpLbs request manager

<interactive>:35:1: error:
    Ambiguous occurrence ‘httpLbs’
    It could refer to either ‘Network.HTTP.Client.httpLbs’,
                             imported from ‘‘Network.HTTP.Client’
                             (and originally defined in ‘http-client-0.5.12.1:Network.HTTP.Client.Core’)
                          or ‘Network.HTTP.Conduit.httpLbs’,
                             imported from ‘Network.HTTP.Conduit’
```

どうやら名前が衝突しているらしい
型を見てみるとどっちでも良い気がするけど、適当に片方を明示して使ってみる

また怒られた

```
> Network.HTTP.Conduit.httpLbs request manager

*** Exception: HttpExceptionRequest Request {
  host                 = "api.github.com"
  port                 = 443
  secure               = True
  requestHeaders       = []
  path                 = "/repos/suzuki-hoge/has-hub-workspace/milestones"
  queryString          = ""
  method               = "GET"
  proxy                = Nothing
  rawBody              = False
  redirectCount        = 10
  responseTimeout      = ResponseTimeoutDefault
  requestVersion       = HTTP/1.1
}
 TlsNotSupported
```

昔何かのライブラリで http しか対応していないのがあったので瞬間嫌な予感がしたけど、https で始まる文字列をちゃんとパースして`secure=True`にもなってるんだから、きっと設定が悪いだけだろう
[http-client](https://hackage.haskell.org/package/http-client-0.5.12/docs/Network-HTTP-Client.html) のマニュアルを適当に`tls`で検索してたら、こんな記述を見つけた

```
defaultManagerSettings :: ManagerSettings

Default value for ManagerSettings.

Note that this value does not have support for SSL/TLS.
If you need to make any https connections, please use the http-client-tls package, which provides a tlsManagerSettings value.
```

けど、なんとなく`:t`してみたらあった

```haskell
> :t tlsManagerSettings 
tlsManagerSettings :: ManagerSettings
```

[http-client-tls](https://hackage.haskell.org/package/http-client-tls-0.3.5.3/docs/Network-HTTP-Client-TLS.html) は？と思ったけど思い切ってこれも気にしないことにし、`manager`を作り直して再実行

```haskell
> Network.HTTP.Conduit.httpLbs request manager
Response {responseStatus = Status {statusCode = 403, statusMessage = "Forbidden"}, ..., responseHeaders = [...], responseBody = "Please make sure your request has a User-Agent header.", ...}
-- 出力はごっそりカット
```

やっと haskell じゃあなくて github に怒られた
けど user-agent がだめらしい、なんで？

ふと思いついて`curl`に`-v`を足して叩いてみたら、こんな出力を発見

```
> User-Agent: curl/7.30.0
```

なんか付いてるけど、どうやら haskell で投げたときは付いてないか別の値なんだろうか

`curl`では`-H 'User-Agent: curl'`で user-agent を書き換えられるので、haskell の方も header の明記をしてみよう

### `Header`を作る
どうやら record 構文で`request`の属性を任意に変更して設定するみたい
なのでとりあえず`:info`してみる

```haskell
> :info Request
data Request = Request {
  method                 :: Network.HTTP.Types.Method.Method,
  secure                 :: Bool,
  host                   :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  port                   :: Int,
  path                   :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  queryString            :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  requestHeaders         :: Network.HTTP.Types.Header.RequestHeaders,
  requestBody            :: RequestBody,
  proxy                  :: Maybe Proxy
} -- 出力整形して属性は抜粋
```

何やら型が書いてあるので、`Network.HTTP.Types.Header`で調べて [http-types](http://hackage.haskell.org/package/http-types-0.12.1/docs/Network-HTTP-Types-Header.html) を見てみる

```haskell
type RequestHeaders = [Header]
```

で

```haskell
type Header = (HeaderName, ByteString)
```

で

```haskell
hUserAgent :: HeaderName
```

だから、なんか行けそうだぞ

いくつかの記事で見たとおり、こんな感じで作ってみる

```haskell
> ua = (hUserAgent, "curl")
```

あとはこれを最初に作った`request`に埋める

```
> request' = request { requestHeaders = [ua] }

<interactive>:9:40: error:
    • Couldn't match type ‘[Char]’
                     with ‘bytestring-0.10.8.2:Data.ByteString.Internal.ByteString’
      Expected type: Header
        Actual type: (HeaderName, [Char])
    • In the expression: ua
      In the ‘requestHeaders’ field of a record
      In the expression: request {requestHeaders = [ua]}
```

うん、怒られた、そんな気はしていた
だって`(HeaderName, ByteString)`じゃあなくて`(HeaderName, String)`だし

なんで見かけるコードは`"..."`で動いてるんだろう

### `OverloadedStrings`
ここらで実はずっと気付いていたけどなんか難しそうだなと思って無視していた以下の記述について調べる

```haskell
{-# LANGUAGE OverloadedStrings #-}
```

かいつまんで言うと、文字列っぽいものは文字列リテラルで書くと型がよしなになるらしい、すごい（真面目な説明はカット）
ghci では`:set -X`で設定する

```haskell
> ua = (hUserAgent, "curl")
> :t ua
ua :: (HeaderName, [Char])

> :set -XOverloadedStrings

> ua = (hUserAgent, "curl")
> :t ua
ua :: Data.String.IsString b => (HeaderName, b)
```

２要素目が文字列っぽいものになった、`request'`を作り直して再実行すべし

```haskell
> request' = request { requestHeaders = [ua] }

> Network.HTTP.Conduit.httpLbs request' manager

Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, ..., responseHeaders = [...], responseBody = "...", ...}
-- 出力はごっそりカット
```

やっと呼べた

## 出力パース
これは知ってる、あれでしょ、`lens`とか言うやつでしょ（何も知らない）

いくつかの json パースの例を見たけど、これだけ知っていればとりあえず良さそう

+ `^?`
+ `nth`
+ `key "xxx"`
+ `_String`, `_Integer`

[lens](https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Fold.html) と [lens-aeson](http://hackage.haskell.org/package/aeson-lens-0.5.0.0/docs/Data-Aeson-Lens.html) を依存に追加して触ってみる

```haskell
import Control.Lens
import Data.Aeson.Lens
```

```haskell
> response <- Network.HTTP.Conduit.httpLbs request' manager

> responseBody response ^? nth 0 . key "number" . _Integer 
Just 2
```

すごーい
それっぽーい

## 次は更新する
ここまでで結構試行錯誤した（たったこれだけでも）ので、根拠の無い自信が付いた

次はこれを実現する

```
$ curl https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones -H "Authorization: token xxx" -X POST -d '{"title": "sprint 1"}'
{
  "number": 4,
  "title": "sprint 1"
}
# 出力はごっそりカット
```

post するって言ったって、要するに`Request`をちゃんと設定すりゃ良いんでしょ
もう一回型を見てみるか

（再掲）

```haskell
> :info Request
data Request = Request {
  method                 :: Network.HTTP.Types.Method.Method,
  secure                 :: Bool,
  host                   :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  port                   :: Int,
  path                   :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  queryString            :: bytestring-0.10.8.2:Data.ByteString.Internal.ByteString,
  requestHeaders         :: Network.HTTP.Types.Header.RequestHeaders,
  requestBody            :: RequestBody,
  proxy                  :: Maybe Proxy
} -- 出力整形して属性は抜粋
```

`method`と`requestBody`を埋めりゃ良さそうだ

### `Method`を設定する
`requestHeaders`の時と同様に、`Network.HTTP.Types.Method`で調べて [http-types](https://hackage.haskell.org/package/http-types-0.12.1/docs/Network-HTTP-Types-Method.html) を発見した

```haskell
methodPost :: Method
```

らしい
ふん、思った通りだ

### `RequestBody`を作る
あとは`requestBody`だけ

```haskell
ghci> :info RequestBody
data RequestBody
  = RequestBodyLBS bytestring-0.10.8.2:Data.ByteString.Lazy.Internal.ByteString
  | RequestBodyBS bytestring-0.10.8.2:Data.ByteString.Internal.ByteString
  | RequestBodyBuilder GHC.Int.Int64 bytestring-0.10.8.2:Data.ByteString.Builder.Internal.Builder
  | RequestBodyStream GHC.Int.Int64 (GivesPopper ())
  | RequestBodyStreamChunked (GivesPopper ())
  | RequestBodyIO (IO RequestBody)
```

なんだ？
よくわかんないけど、とりあえず`RequestBodyLBS`を作れば良いのかな

探してみたらこんな記述を見つけた

```haskell
RequestBodyLBS $ encode requestObject
```

これに使う`requestObject`は、`.=`のやつで作るっぽい
なんか色んなサイトうろちょろしてるときに目に入ってた、早速やってみる

```
ghci> requestObject = object ["title" .= "sprint 1"]

<interactive>:44:33: error:
    Ambiguous occurrence ‘.=’
    It could refer to either ‘Data.Aeson..=’,
                             imported from ‘Data.Aeson’ at /Users/ryo/Dropbox/Developments/haskell/has-hub/app/Main.hs:2:28-31
                             (and originally defined in ‘aeson-1.2.4.0:Data.Aeson.Types.ToJSON’)
                          or ‘Control.Lens..=’,
                             imported from ‘Control.Lens’
                             (and originally defined in ‘Control.Lens.Setter’)
```

まーた名前衝突した
これらふたつは全然違う型っぽいので、前者を明記する

```haskell
> requestObject = object ["title" Data.Aeson..= "sprint 1"]
```

準備は整った（はず）

### 投げる
```haskell
> token = (hAuthorization, "xxx")

> request' = request { requestHeaders = [ua, token], method = methodPost, requestBody = RequestBodyLBS $ encode requestObject }

> Network.HTTP.Conduit.httpLbs request' manager
Response {responseStatus = Status {statusCode = 201, statusMessage = "Created"}, ..., responseHeaders = [...], responseBody = "...", ...}
```

呼べた

出力パースは同じやり方

## 依存整理
ghci で`:t`や`:info`をしたりしつつ、通ったコードはソースに転記したりしていたんだけど、`import`が酷いことになってきたので整理したい

```haskell
--import Network.HTTP.Client as C
--import Network.HTTP.Conduit
--import Network.HTTP.Types.Header
--import Network.HTTP.Types.Method
--import Network.HTTP.Simple
--import Data.Aeson (object, (.=), encode)
--import Control.Lens hiding ((.=))
--import Data.Aeson.Lens
--import Data.Text (Text)
```

試行錯誤したので今はどれを使っているのかもわからないし、こんなに頻繁に名前衝突するってのはなんか間違ってんじゃあないか、という気持ちになる
（実はここまで来るのに不慣れで１日近くかかり、整理しないと限界だったので一息付くためにせっかくと思って記事化した）

こんな事普通はやらないのか、やるまでもないのか、常套手段があるのか、さっぱりわからないけど、とりあえず手当たり次第に使ったものを`:info`して調べていく

![modules_first.png](https://qiita-image-store.s3.amazonaws.com/0/113398/4606ec3f-694b-5261-d91c-a39b2fed712b.png)

少し頭が整理できた

ややこしいから`http-conduit`はやめて`http-client`だけで良さそうな気がしてきた
その代わり明示的に`http-client-tls`を使う

あと出力パース系はそんなにややこしくなかったけど、一応併記した
これらは全て独立している

あと色んな記事を見ていて気付いたんだけど、header と method は文字列リテラルで文字列っぽくできるみたい

## ここまでを反映した成果物
依存はこれら

```
- http-client
- http-client-tls
- aeson
- lens-aeson
- lens
```

コードはこう

```haskell
{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Client (parseRequest_, newManager, httpLbs, requestHeaders, responseBody, method, requestBody, RequestBody(..))
import Network.HTTP.Client.TLS (tlsManagerSettings)

import Control.Lens ((^?))
import Data.Aeson.Lens (nth, key, _Integer)

import Data.Aeson ((.=), encode, object)


get :: IO (Maybe Integer)
get = do
  manager <- newManager tlsManagerSettings

  let request = (parseRequest_ "https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones") {
      requestHeaders = [("User-Agent", "curl")]
  }

  response <- httpLbs request manager
  return $ responseBody response ^? nth 0 . key "number" . _Integer


post :: IO (Maybe Integer)
post = do
  manager <- newManager tlsManagerSettings

  let request = (parseRequest_ "https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones") {
      requestHeaders = [("User-Agent", "curl"), ("Authorization", "token xxx")]
    , method = "POST"
    , requestBody = RequestBodyLBS $ encode $ object ["title" .= ("sprint 1" :: String)]
  }

  response <- httpLbs request manager
  return $ responseBody response ^? key "number" . _Integer
```

使う

```haskell
> get
Just 2

> post
Just 4
```

依存もすっきりした

![modules_second.png](https://qiita-image-store.s3.amazonaws.com/0/113398/66691db7-f038-013e-8b5c-57a91cb81524.png)

## 変数を使う
ハードコードされている文字列を動的に設定したい
具体的には header に使う token と、`Request`に設定する method と title の部分のこと

ただし token と method は`IsString`という文字列っぽいものなので、適当に置き換えても上手くいかない
どうやら`String`ではなくて`"foo"`という文字列リテラルである必要があるみたい

### method
例えば method を引数としてこんな感じで受けて埋めると、見た目は同じっぽいのにコンパイルは通らない

```diff
- post :: IO (Maybe Integer)
- post = do
-     , method = "POST"

+ call :: String -> IO (Maybe Integer)
+ call method = do
+     , method = method
```

なので結局 method は使わなくて済んだと思った`Network.HTTP.Types.Method`を使わないといけないのかな？
それか、こんな感じで適当にマッピングしたら良い

```diff
-     , method = method
+     , method = if method == "GET" then "GET" else "POST"
```

### token
token は出所が設定ファイルなりコマンドライン引数なりになると思うのでハードコードは不可能
しかしどこかで`String -> ByteString`は面倒だとみたのでどうするべきかと色々試行錯誤した

結局コマンドライン引数ならいきなり`ByteString`で手に入れる方法があるみたいなので、それを使うことにする

[unix](http://hackage.haskell.org/package/unix-2.7.1.0/docs/System-Posix-Env-ByteString.html) というモジュールの`getArgs`を使う

```haskell
import System.Posix.Env.ByteString (getArgs)
```

```haskell
args <- getArgs
let token = args !! 0
```

初見の文字列っぽいもの、にかなり手間取ったけどなんとか慣れてきた

## json を使う
ここまでをちゃんと理解できたので、少し json 変換をしてみる

いくつもやり方はあるみたいだけど、今回はインパラを表現する`data`を定義してそれを`key: value`に変換してみる

依存は増えないので`import`だけする

あと`Data.Aeson`から使う関数が非常に多いので、ここは逆に明示的に関数名を指定しないことにする

```haskell
{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson

import GHC.Generics (Generic)
```

### 自前クラス -> json
haskell にフィールド名から勝手に作ってもらうことにした
任意項目は`Maybe`で包んで`omitNothingFields`しておくとキーごと省略してもらえる

```haskell
data MilestoneRequest = MilestoneRequest { title :: String, due_on :: Maybe String } deriving (Show, Generic)

instance ToJSON MilestoneRequest where
  toJSON = genericToJSON defaultOptions { omitNothingFields = True }
```

`encode`により自前クラスから変換できる

```diff
- , requestBody = RequestBodyLBS $ encode $ object ["title" .= ("sprint 1" :: String)]
+ , requestBody = RequestBodyLBS $ encode $ milestoneRequest
```

### json -> 自前クラス
こっちは別の方法を使って自分でキーを指定してみた

```haskell
data Milestone = Milestone Int String deriving Show
instance FromJSON Milestone where
  parseJSON (Object v) = Milestone <$> (v .: "number") <*> (v .: "title")
```

`decode`により自前クラスに変換できる

```haskell
decode $ responseBody response :: Maybe Milestone
```

この辺は素直に`Data.Aeson`で調べれば大丈夫だった
（通信と違いモジュールの種類が多くないのと、記事が多めにあるのが理由）

## 変数を使う（もう一度）
`MilestoneRequest`を作ったので`title`の部分を埋めたいが、これもコマンドライン引数から取ることにするのは当然の流れ

で、こっちは`String`で作ったので、token のためにコマンドライン引数を`ByteString`にしたのと噛み合わない

なので素直にコマンドライン引数は`String`で受け、token は`String -> ByteString`をすることにした

```diff
- import System.Posix.Env.ByteString (getArgs)
+ import System.Environment (getArgs)
```

```haskell
import Data.ByteString.Char8 (pack)

args <- getArgs
let token = pack $ args !! 0
let title = args !! 1
```

`IsString`で混乱していただけで、全くもって大した難易度ではなかった

## エラーハンドリング
milestone の作成は同じ title の milestone が既ににあると以下のような応答になる

```
$ curl https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones -H "Authorization: token xxx" -X POST -d '{"title": "sprint 1"}'

{
  "message": "Validation Failed",
  "errors": [
    {
      "resource": "Milestone",
      "code": "already_exists",
      "field": "title"
    }
  ],
  "documentation_url": "https://developer.github.com/v3/issues/milestones/#create-a-milestone"
}
```

また`title`というキーが無い場合はこうで、

```
$ curl https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones -H "Authorization: token xxx" -X POST -d '{"xxxxx": "sprint 1"}'

{
  "message": "Invalid request.\n\n\"title\" wasn't supplied.",
  "documentation_url": "https://developer.github.com/v3/issues/milestones/#create-a-milestone"
}
```

token が不正な場合はこうなる

```
$ curl https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones -H "Authorization: token xxx" -X POST -d '{"title": "sprint 1"}' 

{
  "message": "Bad credentials",
  "documentation_url": "https://developer.github.com/v3"
}
```

いずれにせよ今回は全部例外にしてしまい、例外通知に`message`を入れてしまうことにした

```haskell
json <- fmap responseBody $ httpLbs request manager

let success = decode json :: Maybe Milestone
let message = fmap unpack $ json ^? key "message" . _String :: Maybe String

case (success, message) of
  (Just x, Nothing) -> return x
  (Nothing, Just x) -> fail x
  (Nothing, Nothing) -> fail "unexpected_error"
```

## 通信以外のテストを書く
動作確認が面倒になってきたので、適当にテストを考える

`MilestoneRequest`を json にする箇所と、json を`Milestone`にする箇所と、戻り次第で戻り値か例外か振り分ける箇所のテストをする
json 変換は本音としては完全に`Data.Aeson`に委譲しているので省略しても良いだろうとは思うけど、せっかくなので書いておく

そのためには通信をする箇所を外から指定できる様にして、実行時とテスト時で違う方法で json を手に入れるようにする

```haskell
data Fetcher = Fetcher (Request -> Manager -> IO L.ByteString)
```

こんなのを作って引数にこれを足し、`Fetcher`の中身は`main`で埋める

また、テスト時は`spec`で固定の文字列を返すようにする

ここまでの差分が結構積み重なっているので、成果物はまとめて

## 最終成果物
ここまでを反映したこの記事の最終成果物はこう（更新系だけ）

依存

```
-- src
dependencies:
- base >= 4.7 && < 5
- http-client
- http-client-tls
- aeson
- lens
- lens-aeson
- bytestring
- text

-- tests
dependencies:
- has-hub
- hspec
- MissingH
```

```haskell:Milestone.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module Milestone where

import GHC.Generics (Generic)

import Control.Applicative ((<$>), (<*>))

import Data.Text (unpack)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy.Internal as L (ByteString)

import Network.HTTP.Client (parseRequest_, newManager, Manager, requestHeaders, method, requestBody, RequestBody(..))
import Network.HTTP.Client.TLS (tlsManagerSettings)

import Network.HTTP.Client (Request, Response)

import Data.Aeson
import Control.Lens ((^?))
import Data.Aeson.Lens (nth, key, _Integer, _String)

-- data

type Token = ByteString

data MilestoneRequest = MilestoneRequest { title :: String, due_on :: Maybe String } deriving (Show, Generic)

instance ToJSON MilestoneRequest where
  toJSON = genericToJSON defaultOptions { omitNothingFields = True }

data Milestone = Milestone Int String deriving (Show, Eq)
instance FromJSON Milestone where
  parseJSON (Object v) = Milestone <$> (v .: "number") <*> (v .: "title")

data Fetcher = Fetcher (Request -> Manager -> IO L.ByteString)

-- function

post :: Fetcher -> Token -> MilestoneRequest -> IO Milestone
post (Fetcher fetcher) token milestoneRequest = do
  manager <- newManager tlsManagerSettings

  let request = (parseRequest_ "https://api.github.com/repos/suzuki-hoge/has-hub-workspace/milestones") {
      requestHeaders = [("User-Agent", "curl"), ("Authorization", token)]
    , method = "POST"
    , requestBody = RequestBodyLBS $ encode $ milestoneRequest
  }

  json <- fetcher request manager

  let success = decode json :: Maybe Milestone
  let message = fmap unpack $ json ^? key "message" . _String :: Maybe String

  case (success, message) of
    (Just x, Nothing) -> return x
    (Nothing, Just x) -> error x
    (Nothing, Nothing) -> error "Unexpected error"
```

```haskell:Main.hs
import System.Environment (getArgs)
import Data.ByteString.Char8 (pack)

import Network.HTTP.Client (httpLbs, responseBody)

import Milestone


main :: IO ()
main = do
  let fetcher = Fetcher $ \request manager -> fmap responseBody $ httpLbs request manager

  args <- getArgs
  let token = pack $ args !! 0
  let title = args !! 1
  let milestoneRequest = MilestoneRequest title Nothing

  post fetcher token milestoneRequest >>= print
```

```haskell:MilestoneSpec.hs
module MilestoneSpec where

import Test.Hspec

import Data.Aeson (encode)

import Data.ByteString.Char8 (pack)
import qualified Data.ByteString.Lazy.Internal as L (ByteString, packChars)

import Data.String.Utils(replace)

import Milestone


toB :: String -> L.ByteString
toB s = L.packChars $ replace "'" "\"" s


dummy :: String -> Fetcher
dummy s = Fetcher $ \_ _ -> return $ toB s

token :: Token
token = pack "xxx"

milestoneRequest :: MilestoneRequest
milestoneRequest = MilestoneRequest "sprint 1" Nothing


spec :: Spec
spec = do
  describe "to json" $ do
    it "no due" $ do
      let mr = MilestoneRequest "sprint 1" Nothing

      encode mr `shouldBe` toB "{'title':'sprint 1'}"

    it "with due" $ do
      let mr = MilestoneRequest "sprint 1" (Just "2018-05-25:59:59Z")

      encode mr `shouldBe` toB "{'due_on':'2018-05-25:59:59Z','title':'sprint 1'}"


  describe "post" $ do
    it "success" $ do
      let fetcher = dummy "{'number': 1, 'title': 'sprint 1'}"

      post fetcher token milestoneRequest `shouldReturn` Milestone 1 "sprint 1"

    it "bad credentials" $ do
      let fetcher = dummy "{'message': 'Bad credentials'}"

      post fetcher token milestoneRequest `shouldThrow` errorCall "Bad credentials"

    it "unexpected error" $ do
      let fetcher = dummy "{'xxx': '???'}"

      post fetcher token milestoneRequest `shouldThrow` errorCall "Unexpected error"
```

```
> :main "token xxx" "sprint 1"
Milestone 1 "sprint 1"

> :main "token xxx" "sprint 1"
*** Exception: Validation Failed
CallStack (from HasCallStack):
  error, called at /path/to/Milestone.hs:57:26 in main:Milestone
```

## 所感
+ 依存が見える方が好きなので、いわゆるアスタリスクインポート的なのはあんまり好きじゃあないな
+ 大体は [http-client](https://hackage.haskell.org/package/http-client-0.5.12/docs/Network-HTTP-Client.html) の例で済む
  + けど、この掲載例がちゃんと読めるようになるために手探りして遠回りしてきた
+ そもそも別のモジュールも引っかかるので、何も知らない状態でいきなりコード例を見せられてもあんまりすっと入ってこない
  + けど一度出来るとそんなに難しくないと思っちゃうな  
+ json パース以外の部分は、コンパイルが通れば結構安心できる
+ `:info`連打してた、結構勉強になるし見つけた型で適当に調べれば動かすってならなんとかなる
+ 例外やモックは今回の興味外

モジュールの依存が絵でぱっと見える方法ってないの？
そこが一番辛かった

けど楽しかったし出来る様になったので良し