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

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知道如何解码返回值!

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

我想将方法​​定义(在我的示例中为登录和注销)移动到单独的模块,但要保持执行功能类似,当然要保持类型安全和Aeson解码.

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

任何提示如何做到这一点都是受欢迎的!

最佳答案 因为你在执行不同命令时唯一不同的是调用buildHttpRequest,我建议使用以下备用数据类型:

type Command a = Tagged a HttpRequest

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

执行类型需要稍微修改才能要求解码附加类型:

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

但是,它的实施基本保持不变;只需用untag替换buildHttpRequest.这些命令不方便内省,但您可以沿模块边界拆分;例如

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 -}
点赞