I am running into a problem setting up a simple proof of concept servant API. This is my User datatype and my API type:
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
The handler method for this uses postgresql-simple like this:
create u = liftIO $ head <$> returning connection "insert into users (first_name, last_name) values (?,?) returning id" [(first_name u, last_name u)]
Boilerplate code such as connecting to the db and the routing method has been elided. The problem is that if I make a POST request, what I want to be doing is creating a new user, so I would supply the JSON:
{ "first_name": "jeff", "last_name": "lebowski" }
but then my program fails at runtime with
Error in $: When parsing the record User of type Lib.User the key id was not present.
This makes sense because the API specified a User which has an id field. But I do not want to have to pass a bogus id in the request (since they are assigned by postgres sequentially) because that is gross. I also can't move the id field out of the User datatype because then postgres-simple fails because of a model-database mismatch when making a GET request to a different endpoint (which does the obvious thing: get by id. Not included above). What do I do here? Write a custom FromJson instance? I already tried setting the Data.Aeson.TH options flag omitNothingFields to True and setting the id field to be a Maybe Int, but that didn't work either. Any advice would be appreciated.
mkUser :: User -> User
make sense? You need to allow clients to pass you a message that has only the info necessary to create aUser
. E.g.,data CreateUser = CreateUser { givenName :: Text, surName :: Text }
,type API = "users" :> ReqBody '[JSON] CreateUser :> Post '[JSON] User
. – R Bnil
, right?). The impedance mismatch here is that a user without an ID is a different thing from a user with a nullable ID is a different thing than a user with a non-nullable ID. There's not really a way to get the type system to lie. I suppose you could come up with a Template Haskell solution that allows you to effectively knock out fields on an existing datatype to produce a new one. I'm not too quick with the TH though so I hope someone else comes along to snipe the answer. – R B