Haskell scotty and persistence rest API - haskell

I'm new in Haskell. I woudl like to create simple crud rest api with scotty and persistence. I'm using sqlite with one table todo (title text, description text), I have some records in table. My point is to show all records on enpoint /todos in json format. When I print it I gets an error
• Couldn't match expected type ‘Data.Text.Internal.Lazy.Text’
with actual type ‘[Entity ToDo]’
• In the first argument of ‘text’, namely ‘(_ToDo)’
In a stmt of a 'do' block: text (_ToDo)
In the second argument of ‘($)’, namely
‘do _ToDo <- liftIO readToDo
text (_ToDo)’
|
69 | text(_ToDo)
| ^^^^^
My code:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
import Data.Monoid ((<>))
import Web.Scotty
import qualified Web.Scotty as S
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Text (Text)
import Data.Time (UTCTime, getCurrentTime)
import qualified Data.Text as T
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Database.Persist.Sql
import Control.Monad (forM_)
import Control.Applicative
import Control.Monad.Logger
import Data.Aeson
import Data.Default.Class
import GHC.Generics
import Control.Monad.IO.Class (liftIO)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
ToDo
title String
description String
deriving Show
|]
runDb :: SqlPersist (ResourceT (NoLoggingT IO)) a -> IO a
runDb = runNoLoggingT
. runResourceT
. withSqliteConn "todo.db"
. runSqlConn
instance ToJSON ToDo where
toJSON(ToDo title description) = object ["title" .= title, "description" .= description]
readToDo :: IO [Entity ToDo]
readToDo = (runDb $ selectList [] [LimitTo 10] )
routes :: ScottyM()
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(_ToDo)
main = do
putStrLn "Starting server...."
scotty 7777 routes
How to correct this?

It looks like compiler accused you of having a bad conversion between the list of Entity ToDo-s and Data.Text.Lazy.
Please try to convert it by hand using pack function:
import Data.Text.Lazy (pack)
...
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(pack . show $ _ToDo) -- explicit conversion
...

Related

Scotty and persistence - type error when using insert function

I've got the following application:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
-- Scotty
import qualified Web.Scotty as S
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.Static
import qualified Data.Text.Lazy as L
-- HTML rendering
import Text.Blaze.Html.Renderer.Text (renderHtml)
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Control.Monad.IO.Class
-- Database
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Control.Monad.Logger
-- URL generation
import System.Random
import Control.Monad (replicateM)
-- JSON
import Data.Map (fromList)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Link
shortUrl L.Text
URLKey shortUrl
Primary shortUrl
longUrl L.Text
counter Int
deriving Show
|]
getURL :: L.Text -> IO (Maybe (Entity Link))
getURL shortId = runSqlite "links.db" $ do
maybeOriginal <- getBy $ URLKey shortId
pure maybeOriginal
-- I don't know what type to give this, that's probably the problem
addURL short long = runSqlite "links.db" $ do
insert $ Link short long
main :: IO ()
main = do
-- Connect to db and run migration
runSqlite "links.db" $ do runMigration migrateAll
S.scotty 3000 $ do
...
S.post "/shorten" $ do
-- Get URL
url <- S.param "url" :: S.ActionM L.Text
-- Generate a random short URL
randStr <- liftIO $ getRandStr 5
-- Add the urls to the database
liftIO $ addURL (L.pack randStr) url
-- Send JSON response with ID
S.json $ fromList [("id" :: String, randStr)]
I get the following error:
shortener> build (lib + exe)
Preprocessing library for shortener-0.1.0.0..
Building library for shortener-0.1.0.0..
Preprocessing executable 'shortener-exe' for shortener-0.1.0.0..
Building executable 'shortener-exe' for shortener-0.1.0.0..
[2 of 2] Compiling Main
/home/henry/haskell/shortener/app/Main.hs:86:5: error:
• Couldn't match type ‘PersistEntityBackend (Int -> Link)’
with ‘SqlBackend’
arising from a use of ‘insert’
• In the first argument of ‘($)’, namely ‘insert’
In a stmt of a 'do' block: insert $ Link short long
In the second argument of ‘($)’, namely
‘do insert $ Link short long’
|
86 | insert $ Link short long
| ^^^^^^
-- While building package shortener-0.1.0.0 (scroll up to its section to see the error) using:
/home/henry/.stack/setup-exe-cache/x86_64-linux-tinfo6/Cabal-simple_mPHDZzAJ_3.4.1.0_ghc-9.0.2 --builddir=.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.4.1.0 build lib:shortener exe:shortener-exe --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I'm not sure how to resolve this type error and I haven't been able to find anything of use online. There was this answer with a similar problem, but the given type signature and several variations of it did not work.
It turns out I forgot a field when inserting. I had
insert $ Link short long
I needed
insert $ Link short long 0
for the counter field of Link.
Unfortunately the error didn't make that at all clear.

Haskell RIO monad inside persistent with pool

Similar question: Haskell / Persistent-Sqlite: "No instance for (Control.Monad.Trans.Resource.MonadResource IO)"
I'm trying the use the selectSource from persistent to read the database in streaming mode and log the values in the console using logInfo.
The following snippets are executed in a RIO project template.
The following code contains the parts that work and don't work.
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Disambiguate.People where
import Data.Conduit
import qualified Data.Conduit.List as CL
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Import hiding (on, (^.))
import Prelude (print)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int
deriving Show
|]
disambiguatePeople :: RIO App ()
disambiguatePeople = do
-- WORKS!
runSqlite ":memory:" insertAndRead
-- DOES NOT COMPILE!
withSqlitePool ":memory:" 1 $ \pool -> do
liftSqlPersistMPool insertAndRead pool
where
insertAndRead = do
runMigration migrateAll
insert_ $ Person "John Doe" 35
insert_ $ Person "Jane Doe" 50
runConduit $ selectSource [PersonAge >. 5] [] .| CL.mapM_
(lift . logInfo . displayShow)
I get the following error:
src/Run.hs:37:25: error:
• Couldn't match type ‘RIO App’ with ‘IO’
Expected type: ReaderT
SqlBackend
(monad-logger-0.3.35:Control.Monad.Logger.NoLoggingT
(Control.Monad.Trans.Resource.Internal.ResourceT IO))
()
Actual type: ReaderT
SqlBackend
(monad-logger-0.3.35:Control.Monad.Logger.NoLoggingT
(Control.Monad.Trans.Resource.Internal.ResourceT (RIO App)))
()
• In the first argument of ‘liftSqlPersistMPool’, namely
‘insertAndRead’
In a stmt of a 'do' block: liftSqlPersistMPool insertAndRead pool
In the expression: do liftSqlPersistMPool insertAndRead pool
|
37 | liftSqlPersistMPool insertAndRead pool
| ^^^^^^^^^^^^^
I understand the error since the function liftSqlPersistMPool hardcoded the IO type.
How can I solve this to use it with the RIO type ?

QuasiQuotes with OverloadedLabels

I'm trying to use OverloadedLabels with QuasiQuotes from here package. Using plain lenses works but #foo fails with parsing error during compilation. So does field #"foo". Is there a deeper reason this will not work or could it be be a bug in here's interpolated parser?
{-# language DataKinds #-}
{-# language DeriveGeneric #-}
{-# language DerivingStrategies #-}
{-# language OverloadedLabels #-}
{-# language OverloadedStrings #-}
{-# language TypeApplications #-}
{-# language QuasiQuotes #-}
import Control.Lens
import Data.Text (Text)
import qualified Data.Text.IO as T
import Data.Generics.Product
import Data.Generics.Labels
import Data.String.Here
import GHC.Generics (Generic)
data Test = Test
{ name :: Text
} deriving stock (Eq, Show, Generic)
_name :: Lens' Test Text
_name f (Test a) = fmap (\a' -> Test a') (f a)
t :: Test
t = Test "test"
test :: IO ()
test = do
-- ok
T.putStrLn $ t ^. field #"name"
T.putStrLn $ t ^. #name
putStrLn [i|${t ^. _name}|]
-- parse error
putStrLn [i|The name is ${t ^. field #"name"}|]
putStrLn [i|The name is ${t ^. #name}|]
Error for #name:
test.hs:36:12: error:
• Exception when trying to run compile-time code:
Failed to parse interpolated expression in string: The name is ${t ^. #name}
(line 1, column 25):
0
SrcLoc "" 1 6
Parse error in expression: t ^.
CallStack (from HasCallStack):
error, called at src/Data/String/Here/Interpolated.hs:64:33 in here-1.2.13-HU0AD0x0dD36rY9YuL1gwE:Data.String.Here.Interpolated
Code: Language.Haskell.TH.Quote.quoteExp
i "The name is ${t ^. #name}"
• In the quasi-quotation: [i|The name is ${t ^. #name}|]
|
36 | putStrLn [i|The name is ${t ^. #name}|]
|
It looks like haskell-src-meta doesn’t support OverloadedLabels yet. The haskell-src-exts parser has an OverloadedLabels case, but haskell-src-meta doesn’t have a case for it in the ToExp instance for Exp. I guess the “unsupported” error message from haskell-src-meta is getting swallowed by the error handling in here.
Just in case anyone else stumbles upon this, I've made a PR to fix this in haskell-src-meta: https://github.com/DanBurton/haskell-src-meta/pull/19

Get id from Entity record

I have an Entity record, specifically Entity User and I need to extract the Id which the User has in the database as an Int.
From reading the docs it seems entityKey would be useful here but I'm not quite sure how I would go about getting an Int out.
You have to use a combination of fromSqlKey and entityKey. A sample program demonstrating it:
#!/usr/bin/env stack
{- stack
--resolver lts-9.0
--install-ghc
runghc
--package persistent
--package persistent-sqlite
--package persistent-template
--package mtl
-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.IO.Class (liftIO, MonadIO)
import Control.Monad.Reader
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Int
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]
insertPerson :: MonadIO m => ReaderT SqlBackend m (Key Person)
insertPerson = insert $ Person "Michael" $ Just 26
main :: IO ()
main = runSqlite ":memory:" $ do
runMigration migrateAll
michaelId <- insertPerson
(michael :: Entity Person) <- getJustEntity michaelId
liftIO $ print $ (fromSqlKey . entityKey $ michael :: Int64)
And it's output:
~/g/scripts $ stack persist.hs
Migrating: CREATE TABLE "person"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NULL)
1

Haskell Noob: QuasiQuotes OverloadedStrings in Yesod not Working?

I'm not sure why I cant get this working....
I have QuasiQuotes and OverloadedStrings at the top of my hs file, at the top of main.hs, maindevel.hs and also declared in my foo.cabal file.
The ERROR:
Couldn't match type ‘[Char]’ with ‘Text’
Expected type: Text
Actual type: String
In the second argument of ‘($)’, namely
‘renderHtml (GHC.Base.id (toHtml y))’
In the expression:
putStrLn $ renderHtml (GHC.Base.id (toHtml y))
Code:
module Widgets.MainWidgets where
{-# LANGUAGE GADTs #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-}
import Import
import Text.Hamlet (shamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Char (toLower)
import Data.List (sort)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import ClassyPrelude
import Yesod
import Data.Time.Clock
import Data.Time.Calendar
import Data.Time.LocalTime
import Data.Time.Format
getCurrYear :: String
getCurrYear = formatTime defaultTimeLocale "%y" getCurrentTime
getYear :: IO ()
getYear = putStrLn $ renderHtml
[shamlet|#{y}|]
where y = getCurrYear
I also have this in my foo.cabal file:
extensions: TemplateHaskell
QuasiQuotes
OverloadedStrings
NoImplicitPrelude
CPP
MultiParamTypeClasses
TypeFamilies
GADTs
GeneralizedNewtypeDeriving
FlexibleContexts
EmptyDataDecls
NoMonomorphismRestriction
DeriveDataTypeable
ViewPatterns
TupleSections
RecordWildCards
TypeSynonymInstances
FlexibleInstances
DeriveGeneric
Any idea why I get the String/Text conversion error?
Thanks in advance!
Adrian
The putStrLn from ClassyPrelude takes a Text, not a String. The simplest solution would be to change the Renderer import to the Text version of the module.

Resources