以类型安全的方式建模POST API

时间:2016-02-11 21:32:47

标签: haskell typeclass gadt

Haskell初学者,尝试以安全的方式包装HTTP REST API并使用自动Aeson解码返回值。我开始为每个API调用使用Haskell函数。这有点模糊,但还可以。

我希望将每个API调用转换为自己的数据类型。例如,对于登录,我会将其建模为指定方法的Login类型,方法参数的Credentials类型和API调用结果的LoginReponse。参数和响应类型当然具有相应的FromJSON和ToJSON实例。

对于两个API调用,它看起来像这样(使用GADT):

data Void           = Void
data Credentials    = Credentials {...}
data LoginResponse  = LoginResponse {...}
data LogoutResponse = LogoutResponse {...}

data Command a where
    Login  :: Credentials -> Command LoginResponse
    Logout :: Void        -> Command LogoutResponse

execute :: FromJSON a => Command a -> IO a
execute cmd = do
    manager <- newManager tlsManagerSettings
    let request = buildHttpRequest cmd

    result <- httpLbs request manager
    let body = responseBody result
    let parsed = fromJust $ decode body

    return parsed

这对我的用例很有用 - 我可以在执行它们之前内省命令,我无法构造无效的API调用,而且Aeson知道如何解码返回值!

这种方法的唯一问题是我必须在单个数据声明下将所有命令保存在单个文件中。

我想将方法​​定义(在我的示例LoginLogout中)移到单独的模块中,但要使execute函数保持相似,当然要保持类型安全和艾森解码。

我尝试使用类型类创建一些东西,但无处可去。

欢迎任何提示如何做到这一点!

1 个答案:

答案 0 :(得分:1)

由于execute中针对不同命令的唯一不同之处是调用buildHttpRequest,我建议使用以下备用数据类型:

type Command a = Tagged a HttpRequest

(我不知道buildHttpRequest的返回类型,所以我做了一些事情,并假设它返回了HttpRequest。希望这个想法很清楚,即使我是这样的。我确定我错了这部分。)Tagged类型来自tagged,并允许您将类型附加到值;它是urphantom类型。在我们的示例中,我们将使用附加类型来决定如何在execute步骤中解码JSON。

execute的类型需要稍加修改才能要求解码附加类型:

execute :: FromJSON a => Command a -> IO a

然而,其实施基本保持不变;只需将buildHttpRequest替换为untag即可。这些命令不方便内省,但您可以沿模块边界拆分; e.g。

module FancyApp.Login (Credentials(..), LoginResponse(..), login) where

import FancyApp.Types

data Credentials = Credentials
data LoginResponse = LoginResponse

login :: Credentials -> Command LoginResponse
login = {- the old implementation of buildHttpRequest for Login -}

module FancyApp.Logout (LogoutResponse(..), logout) where

import FancyApp.Types

data LogoutResponse = LogoutResponse

logout :: Command LogoutResponse
logout = {- the old implementation of buildHttpRequest for Logout -}