4

我在设置一个简单的概念证明服务 API 时遇到了问题。这是我的用户数据类型和我的 API 类型:

data User = User { id :: Int, first_name :: String, last_name :: String } deriving (Eq, Show, Generic)
instance FromRow User
instance ToRow User
$(deriveJSON defaultOptions ''User)

type API = "users" :> ReqBody '[JSON] User :> Post '[JSON] User

处理程序方法使用 postgresql-simple,如下所示:

create u = liftIO $ head <$> returning connection "insert into users (first_name, last_name) values (?,?) returning id" [(first_name u, last_name u)]

已经省略了连接数据库和路由方法等样板代码。问题是,如果我发出 POST 请求,我想做的是创建一个新用户,所以我会提供 JSON:

{ "first_name": "jeff", "last_name": "lebowski" }

但是然后我的程序在运行时失败了

Error in $: When parsing the record User of type Lib.User the key id was not present. 

这是有道理的,因为 API 指定了一个具有 id 字段的用户。但我不想在请求中传递一个虚假的 id(因为它们是由 postgres 顺序分配的),因为那很恶心。我也无法将 id 字段移出 User 数据类型,因为在向不同的端点发出 GET 请求时,由于模型数据库不匹配而导致 postgres-simple 失败(这很明显:通过 id 获取。不包括在上面)。我在这里做什么?编写自定义 FromJson 实例?我已经尝试将 Data.Aeson.TH 选项标志 omitNothingFields 设置为 True 并将 id 字段设置为 Maybe Int,但这也不起作用。任何意见,将不胜感激。

4

1 回答 1

4

首先你要明白一个User和对应这个User的表中的一行是两个不同的东西。

一行有一个id,一个用户没有。例如,您可以想象在不处理 id 或他们是否被保存的情况下比较两个用户。

一旦确信,您将不得不向类型系统解释这一点,或者您将不得不处理 Maybe 字段,我认为这不是这里的解决方案。

有人谈到 Template Haskell,我觉得这里有点矫枉过正,需要先解决问题。

您可以做的是使用数据类型来表示数据库中保存的行。我们称它为实体。

newtype PrimaryKey = PrimaryKey Int

data Entity b = Entity PrimaryKey b

然后在你的数据库中保存用户行的函数可以接受一个user作为参数并返回一个PrimaryKey(当然在你的数据库单子中)。从数据库读取的其他函数将使用Entity User

您的字段声明不会重复,因为您将 User 类型重新用作参数。

您必须相应地调整 FromRow/ToRow 和 FromJSON/ToJSON。

于 2016-08-08T00:08:55.237 回答