我遇到了设置简单的概念验证服务API的问题。这是我的User数据类型和我的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)]
已经省略了诸如连接到db和路由方法之类的Boilerplate代码。 问题是,如果我发出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字段的User。但是我不想在请求中传递伪造的id(因为它们是由postgres顺序分配的),因为这很糟糕。我也无法将id字段移出User数据类型,因为当向另一个端点发出GET请求时,由于模型数据库不匹配而导致postgres-simple失败(这很明显:get by id。包括在上面)。 我该怎么办?编写自定义FromJson实例?我已经尝试将Data.Aeson.TH选项标记omitNothingFields设置为True并将id字段设置为Maybe Int,但这也不起作用。 任何建议将不胜感激。
答案 0 :(得分:3)
首先,您需要了解用户和与此用户对应的表中的行是两个不同的东西。
一行有id,用户没有。例如,您可以想象在不处理ID的情况下比较两个用户,或者保存它们的事实。
一旦确信,您将不得不向类型系统解释这一点,或者您将不得不处理可能字段,我认为这不是解决方案。
有些人谈到模板Haskell,我认为这样做太过分了,你需要先解决问题。
您可以使用数据类型来表示数据库中已保存的行。我们称之为实体。
newtype PrimaryKey = PrimaryKey Int
data Entity b = Entity PrimaryKey b
然后,在数据库中保存用户行的函数可以使用user
作为参数并返回PrimaryKey
(当然,在您的数据库monad中)。
从数据库读取的其他函数将使用Entity User
您的字段声明不会重复,因为您正在重复使用用户类型作为参数。
您必须相应地调整FromRow / ToRow和FromJSON / ToJSON。