• 作成:

servant-clientの新しいバージョンではステータスコードのチェックが厳密になっていました

Postのような汎用的な名前でなく厳密なPostCreatedなどを使えば解決。

問題

servant-clientを、 GHC 9.2でコンパイルするために、まだリリースされてないGitHubに上げられているバージョンに切り替えたら、とあるAPIが常にFailureResponse例外になって呼び出せなくなりました。

原因

そのAPIは200ではなく201で成功を表します。

最新バージョンではどこで決定しているのかは、 Proxyで型経由で呼び出していて複雑さに追うのをとりあえずやめたのですが、

runRequestAcceptStatus (Just [status]) req

https://github.com/haskell-servant/servant/blob/38f519a290da2d4eca4effc39ad96b4ab04ba80e/servant-client-core/src/Servant/Client/Core/HasClient.hs#L258

あたりを見る限り、少なくとも成功ステータスコードを一つに絞っているようです。

解決を模索

Proxyからstatusコードを取り出しているのならばAPI設定で、このAPIは201を返すと指定してやれば良さそうです。

返り値Foo(WithStatus 201 Foo)に変えてやることで可能?

ただ単純にWithStatusを追加するだけだとhoistClientした後、型がWithStatusのついたものになってしまいます。

ステータスコードなんてAPI部分で閉じてしまって成功失敗だけ分かれば良いので解きたいですね。

そもそも呼び出してもWithStatusがついて剥がれない…

雑にパターンマッチしろということか?

WithStatus x <- hoistClient fooApi (toRIO (config ^. fooEndpoint)) (client fooApi) req

でもこれでもエラーのまま。

よくわからないのでとりあえず型は置いといてラップしたままVerb形式をshowしてみることにします。

type FooApi
  = "tokens" :> ReqBody '[JSON] FooConf :> UVerb 'POST '[JSON] '[WithStatus 201 Foo]

Postじゃなくて全て大文字のPOSTになることに注意。

これだとUnionWithStatusでラップされてますが実行できることがわかりました。後はアンラップするだけですね。

matchUnionfoldMapUnionで適切にアンラップする方法がイマイチわからない…

単純な解決

なんで結果が一つかエラーだけに定まるのに、結果のUnionと私は戦っているんだと疑問になってきたので、 UじゃないVerbを調べてみたら普通に存在しました。

-- | 'POST' with 201 status code.
type PostCreated = Verb 'POST 201

servant/Verbs.hs at 38f519a290da2d4eca4effc39ad96b4ab04ba80e · haskell-servant/servant

これ使って、

type FooApi
  = "tokens" :> ReqBody '[JSON] FooConf :> PostCreated '[JSON] Foo

すれば何も思い悩むことはなくなります。実装も一切変更不要です。適切に検索して厳密な型を使うだけでしたね。