Database-backed REST API with servant? - haskell

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.
newtype PrimaryKey = PrimaryKey Int
data Entity b = Entity PrimaryKey b
Then the function saving a User row in your database could take a user as parameter and return a PrimaryKey (In you database monad, of course).
Other functions reading from the database would return something using Entity 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.

Related

default values for PersistList on SQLite - Yesod

I would like to add a PersistList value into a user entity with a default value. My model file looks like this. And Models.hs file:
User
ident Text
password Text Maybe
UniqueUser ident
perms [Privileges] default=[PrvDemoOne]
deriving Typeable
data Privileges =
PrvDemoOne -- ^ what can be demo one...
| PrvDemoTwo -- ^ what can be demo two...
deriving (Show,Read,Eq)
derivePersistField "Privileges"
the code compiles but when a new user is added into the table save an empty array instead of an array with the default value.
1|google-uid:223344555661778819911||[]
The question is how I could save the column with the default value?
Have you read this? The value of the default field doesn't really have anything to do with the Haskell side per-se, it's being passed to set the "default value" description your DBMS. In this case [PrvDemoOne] is being passed directly to SQLite, which will interpret it as gibberish (because it's not a valid SQL expression) so this is either ignored or (what seems to be the case here) treated as if you hadn't set a default at all.
If you want a "Haskell side" default value you should just create a function for that, i.e. something like
defaultUser :: Text -> Maybe Text -> User
defaultUser i maybePw = User { ident = i, password = maybePw, perms = [PrvDemoOne] }
If you want a SQL side default you need to write the corresponding SQL expression for the value you're trying to represent.
On a non-Haskell related note: the 'normal' way to represent lists (or sets in this case, rather!) in SQL is via relations, so you'd normally have a many-to-many relationship mapping users to their privileges instead of a list field.

How do I store Either (Key a) (Key b)?

I have the following model:
User
...
Group
...
Sharing
objectId (Either UserId GroupId)
In Sharing entity I want to store either UserId or GroupId and differentiate between them. Simply using Either doesn't work:
Not in scope: type constructor or class `UserId'
Not in scope: type constructor or class `GroupId'
Adding a new sum-type also doesn't work:
data SharingIdType = SharingUserId UserId | SharingGroupId GroupId
Not in scope: type constructor or class `SharingIdType'
Moving SharingIdType into another module isn't possible, because it uses UserId and GroupId types. The only way I see is to create an entity for each sharing type, like UserSharing/GroupSharing.
Other than that, how to approach this problem?
After searching for some time and thinking about it I concluded there are two possible solutions:
1.
If number of SharingIdTypes is static or rarely changes (means, it is OK to recompile the source to change it or alter the DB schema), the proper way to handle the problem is to have to entities for each sharing type:
User
...
Group
...
UserSharing
userId UserId
GroupSharing
groupId GroupId
Here the "sumness" of the problem is moved to DB queries. Whenever I need to find out with what something shared, I make two selectLists and query two tables instead of one.
2.
If number of SharingIdTypes needs to be altered dynamically, the SharingType entity is needed:
User
...
Group
...
SharingType
description String
Sharing
objectId SharingTypeId
This table is filled up with values corresponding to SharingIdTypes constructors:
do
insert $ SharingType "user"
insert $ SharingType "group"
Now whenever we share something, we refer SharingTypeId.

How to deal with incomplete JSON/Record types (IE missing required fields which I'll later fill in)?

EDIT: For those with similar ailments, I found this is related to the "Extensible Records Problem", something I will personally research more into.
EDIT2: I have started to solve this (weeks later now) by being pretty explicit about data types, and having multiple data types per semantic unit of data. For example, if the database holds an X, my code has an XAction for representing things I want to do with an X, and XResponse for relaying Xs to an http client. And then I need to build the supporting code for shuttling bits between instances. Not ideal, but, I like that it's explicit, and hopefully when my models crystallize, it shouldn't really need much up keep, and should be very reliable.
I'm not sure what the correct level of abstraction is for tackling this problem (ie records? or Yesod?) So I'll just lay out the simple case.
Simple Case / TL;DR
I want to decode a request body into a type
data Comment = Comment {userid :: ..., comment :: ...}
but actually I don't want the request body to contain userid, the server will supply that based on their Auth Headers, (or wherever I want to get data to default fill a field).
So they actually pass me something like:
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
And I turn it into a Comment. But maintaining both nearly-identical types simultaneously is a hassle, and not DRY.
How do I solve this problem?
Details on Problem
I have a record type:
data Comment = Comment {userid :: ..., comment :: ...}
I have a POST route:
postCommentR :: Handler Value
postCommentR = do
c <- requireJsonBody :: (Handler Comment)
insertedComment <- runDB ...
returnJson insertedComment
Notice that the Route requires that the user supply their userid (in the Comment type, which is at least redundant since their id is associated with their auth headers. At worst, it means I need to check that users are adding their own id, or throwing away their supplied id, in which case why did they supply it in the first case.
So, I want a record type that's Comment minus userid, but I don't know how to do that intelligently.
My Current (awful but working) Solution
So I made a custom type with derived FromJSON (for the request body) which is almost completely redundant with the Comment type.
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
Then my new route needs to decode the request body according to this, and then merge a SimpleComment with a userid field to make it a Comment:
postComment2R :: Handler Value
postComment2R = do
c <- requireJsonBody :: (Handler SimpleComment)
(uid, _) requireAuthPair
insertedComment <- runDB $ insertEntity (Comment { commentUserid = uid
, commentComment = comment c})
returnJson ...
Talk about boilerplate. And my use case is more complex than this simple Comment type.
If it factors in, you might be able to tell, I'm using the Yesod Scaffolding.
What I usually do to get a type minus a field is just to have a function which take that field and return the type. In your case you just need to declare an JSON instance for UserId -> Comment. Ok it doesn't seem natural and you have to go it manually but it actually works really well, especially as there is only one field of type UserId in Comment.
A solution I like is to use a wrapper for things that come from/go to the DB:
data Authenticated a = Authenticated
{ uid :: Uid
, thing :: a
} deriving (Show)
Then you can have Comment be just SimpleComment and turn it into an Authenticated Comment once you know the user id.
I'm also looking for a nice way to solve this. :-)
What I usually do in my code is to operate directly on the Aeson's type Value. This is some of the sample code taken from my current project:
import qualified Data.HashMap.Strict as HM
removeKey :: Text -> Value -> Value
removeKey key (Object xs) = Object $ HM.delete key xs
removeKey _ ys = ys
I directly operate on the value Object and remove the particular key present in the javascript object.
And in the Yesod handler code, I do this processing:
myHandler :: Handler RepJson
myHandler = do
userId <- insert $ User "sibi" 23
guser <- getJuser user
let guser' = removeKey "someId" $ toJSON guser
return $ repJson $ object [ "details" .= guser' ]
In some cases, I actually want to add some specific key to the outgoing JSON object. For those, I have specific helper functions defined which operate on the type Value. While this is not perfect, it has been helping me to avoid a lot of boilerplate code.

How to simply all "data" definitions having the same kind and name of a field?

I have many "data" which represent sql table in a database.
data User = User { userId :: Int, userName :: String }
data Article = Article { articleId :: Int, articleTitle :: String, articleBody :: String }
-- .......
All of them has the field "id" as a primary key. I wonder, is there any way to get rid of necessity to define it each time for each "data", can I anyhow simplify that? If do this:
class DataTable a where
myId :: Int
it won't change anything, will? I'll still have to define "id" for each data and then implement it for DataTable, in fact it'll make more complex.
Sometimes it's best to separate things.
data Identified a = Identified
{ ident :: !Int
, payload :: a }
Now you can deal with identified things in an entirely uniform way.
In GHC 8.0 with DuplicateRecordFields you can use the same record field name for many data types. This is part of the larger feature of OverloadedRecordFields.
Part 3, MagicClasses, introduces derivable type classes for HasField and UpdateField. This is similar in idea to your DataTable.
If you want a field in your data type then you must declare it in the data type definition. I am not aware of any extensions, other than Template Haskell, which change this.

Define and use a Text column as id in persistent mongodb

I'm trying to define and use a table (AKA collection) with a Text column as the unique id. I'm using the 2.0.2 versions of persistent, persistent-mongodb, persistent-template.
This is my table definition:
let mongoSettings = (mkPersistSettings (ConT ''MongoContext)) { mpsGeneric = False }
in share [mkPersist mongoSettings, mkMigrate "migrateAll"][persistUpperCase|
User id=myid
myid Text
count Int
|]
I can't figure out how to create a Key User from a Text value.
I attempted:
mkUser :: Text -> Key User
mkUser x = UserKey . MongoKey $ x
with compiler error:
Couldn't match type ‘Text’ with ‘ObjectId’
Expected type: Text -> Key User
Actual type: ObjectId -> Key User
Are mongo keys in persistent only allowed to be of type ObjectId? From my examination of the relevant source code, this seems to be the case, but I would love to be wrong. Mongodb certainly allows textual keys outside of persistent. In fact, it allows for any unique thing.
I also tried inserting a row like so:
insert $ User "bob" 0
but the resulting database row has an auto-generated ObjectId in the _id column, instead of "bob".
If I define the schema:
User sql=profiles
_id Text
count Int
then I still have the issue of attempting to define a Key User with a Text value.
This is a new feature in persistent-mongoDB (that was not available when I asked the question), so the answer used to be "No, that's not possible" but is now "Yes, and here is how". It's available as of the 2.0.3 series: persistent-2.0.3, persistent-template-2.0.3, and persistent-mongoDB-2.0.3.
Your schema should have a line: Id <type of _id field> For example:
let mongoSettings = (mkPersistSettings (ConT ''MongoContext)) { mpsGeneric = False }
in share [mkPersist mongoSettings][persistUpperCase|
User
Id Text
age Int
|]
and you insert documents with a specific _id like this:
insertKey (UserKey "alice") $ User 1
Also see the wiki.

Resources