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.
First you need to understand that a User and a row in a table corresponding to this User are two different things.
A row has an id, a user does not. For example, you can imagine to compare two users without dealing with ids, or the fact that they are saved or not.
Once convinced, you will have to explain this to the type system, or you will have to deal with Maybe fields, which I think is not the solution here.
Some people talked about Template Haskell, I think it would be overkill here, and you need to solve the problem first.
What you can do is use a data type to represent a saved row in your database. Let's call it an Entity.
Then the function saving a User row in your database could take a
user
as parameter and return aPrimaryKey
(In you database monad, of course). Other functions reading from the database would return something usingEntity User
Your field declarations are not duplicated, since you are reusing the User type as a parameter.
You will have to adapt FromRow/ToRow and FromJSON/ToJSON accordingly.