Yesod: Using Github API v3 Library for Haskell - haskell

I'm working on a project that builds on the simple yesod template. I am new to functional programming, haskell and Yesod so it's probably something obvious to anyone with Yesod experience. At the moment I am trying to make github API calls using this library. I am getting some type issues and I'm not even sure how to start approaching solving them.
You can find my handler here.
Handler/Home.hs:43:19:
Couldn't match expected type ‘HandlerT
App IO (Either a0 GitHub.User)’
with actual type ‘GitHub.Request k0 GitHub.User’
In a stmt of a 'do' block:
possibleUser <- GitHub.userInfoForR "mike-burns"
In the expression:
do { maid <- maybeAuthId;
possibleUser <- GitHub.userInfoForR "mike-burns";
result <- either (("Error: " <>) . tshow) formatUser possibleUser;
defaultLayout
(do { (asWidgetT GHC.Base.. toWidget)
((blaze-markup-0.7.1.1:Text.Blaze.Internal.preEscapedText
GHC.Base.. Data.Text.pack)
"<p>Your current auth ID: ");
(asWidgetT GHC.Base.. toWidget) (toHtml (show maid));
(asWidgetT GHC.Base.. toWidget)
((blaze-markup-0.7.1.1:Text.Blaze.Internal.preEscapedText
GHC.Base.. Data.Text.pack)
"</p>\n");
.... }) }
Handler/Home.hs:44:38:
Couldn't match type ‘Text’ with ‘HandlerT App IO a1’
Expected type: a0 -> HandlerT App IO a1
Actual type: a0 -> Text
In the second argument of ‘(.)’, namely ‘tshow’
In the first argument of ‘either’, namely
‘(("Error: " <>) . tshow)’
Handler/Home.hs:44:45:
Couldn't match type ‘Text’ with ‘HandlerT App IO a1’
Expected type: GitHub.User -> HandlerT App IO a1
Actual type: GitHub.User -> Text
In the second argument of ‘either’, namely ‘formatUser’
In a stmt of a 'do' block:
result <- either (("Error: " <>) . tshow) formatUser possibleUser

The GitHub library seems to be about building requests, and running them. The userInfoForR does such a thing :
userInfoForR :: Name User -> Request k User
Once you have a request, you can run it with one of the following functions, depending if you need to authenticate or not:
executeRequest :: Auth -> Request k a -> IO (Either Error a)
executeRequest' :: Request RO a -> IO (Either Error a)
I don't know about this specific case, but let's say you don't need authentication. So, the following expression would do the trick:
executeRequest' (userInfoForR "mike-burns") :: IO (Either Error User)
Now, in order to use it in a Handler, you'll need to learn about the fact that Handler is an instance of MonadIO, and you can thus do:
euser <- liftIO (executeRequest' (userInfoForR "mike-burns"))
case euser of
Left rr -> ...
Right user -> ...

Related

Mystery of subsite types

I can not figure out what types should go in my Foundation.hs when implementing type classes from the authentication plugin / it's use of the auth subsite:
I can feel that I am very close, but I lack understanding. I am simply trying to use a different layout for the login/registration pages.
In Foundation.hs:
instance YesodAuthSimple App where
type AuthSimpleId App = UserId
...
-- Works
onRegisterSuccess :: YesodAuthSimple site => AuthHandler site Html
onRegisterSuccess = authLayout $ [whamlet|
$newline never
<div>
<h1>You Registered successfully.
<p>
Some text here.
|]
-- Works when I do not write a type signature
loginTemplate toParent mErr = $(widgetFile "authpartials/login")
-- Does not work with or without type signatures
customAuthLayout widget = do
master <- getYesod
mmsg <- getMessage
muser <- maybeAuthPair
mcurrentRoute <- getCurrentRoute
pc <- widgetToPageContent $ do
$(widgetFile "custom-auth-layout")
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
The 432:15 is referring to the widgetToPageContent call.
In the type class definition Simple.hs:
class (YesodAuth site, PathPiece (AuthSimpleId site)) => YesodAuthSimple site where
type AuthSimpleId site
...
customAuthLayout :: WidgetFor site () -> AuthHandler site Html
...
I pasted in the definition of customAuthLayout from defaultLayout from Foundation.hs
Here is the error I get from GHC:
Foundation.hs:432:15: error:
• Could not deduce: m ~ HandlerFor App
from the context: MonadAuthHandler App m
bound by the type signature for:
customAuthLayout :: WidgetFor App () -> AuthHandler App Html
at src/Foundation.hs:(427,5)-(434,79)
‘m’ is a rigid type variable bound by
the type signature for:
customAuthLayout :: WidgetFor App () -> AuthHandler App Html
at src/Foundation.hs:(427,5)-(434,79)
Expected type: m (PageContent (Route App))
Actual type: HandlerFor App (PageContent (Route App))
• In a stmt of a 'do' block:
pc <- widgetToPageContent
$ do (do do (asWidgetT GHC.Base.. toWidget)
((blaze-markup-0.8.2.2:Text.Blaze.Internal.preEscapedText
GHC.Base.. Data.Text.pack)
"<!-- custom-auth-layout -->
<body class="d-flex align-items-center bg-auth border-top border-top-2 border-primary">")
....)
In the expression:
do master <- getYesod
mmsg <- getMessage
muser <- maybeAuthPair
mcurrentRoute <- getCurrentRoute
....
In an equation for ‘customAuthLayout’:
customAuthLayout widget
= do master <- getYesod
mmsg <- getMessage
muser <- maybeAuthPair
....
|
432 | pc <- widgetToPageContent $ do
| ^^^^^^^^^^^^^^^^^^^^^^^^...
I have used this tutorial successfully for normal (non-subsite pages) https://ersocon.net/cookbooks/yesod/html-and-seo/custom-layouts
But I am getting tripped up by the subsite types. I have read Michael Snoyman's very good old blog post on subsite types but I cannot understand GHC's error message.
I suspect either the type signature in Simple.hs is wrong, or I am missing something from the function definition.
Try to add liftHandler before widgetToPageContent:
...
pc <- liftHandler $ widgetToPageContent $ do
$(widgetFile "custom-auth-layout")
...
Key lines in the error message are:
Could not deduce: m ~ HandlerFor App
...
Expected type: m (PageContent (Route App))
Actual type: HandlerFor App (PageContent (Route App))
It is basically telling us that it expected a more generic type m, but instead it got a HandlerFor App. So the solution is simply to lift the call to widgetToPageContent using the liftHandler function.
To elaborate further, if we look at the type signature of the function widgetToPageContent, we see that it returns HandlerFor site (PageContent (Route site)). In this case, site instantiates to App, and that is the HandlerFor App (PageContent (Route App)) you see in the error message.
Similarly, your customLayout function returns AuthHandler site Html. AuthHandler is just a type synonym that constrains site to a type equivalent to HandlerSite m which is also an instance of YesodAuth. This also resolves to App, and that is why we get MonadAuthHandler App m and m (PageContent (Route App)) in the error message.

Haskell Yesod get user credentials in subsite

I'm writing a subsite for my yesod project, and I need to display the logged in user's name on that subsite (I'm using yesod-auth hardcoded where the type of AuthId master = Text).
However, the user is logged in on the master site. I'm able to get a value of type AuthId master using maybeAuthId, but I am unable to display this value because it is not an instance of Show.
Is there a type constraint I can put on my Handler to make sure the type of AuthId master derives Show?
getSubsiteHomeR :: (YesodAuth master) => HandlerT Subsite (HandlerT master IO) Html
getSubsiteHomeR = do
lift $ do
maid <- maybeAuthId -- I want to display the value of 'maid'
liftIO $ print maid
defaultLayout [whamlet|.......|]
EDIT: Here is the error message:
Could not deduce (Show (AuthId master))
arising from a use of `print'
from the context: YesodAuth master
bound by the type signature for:
getSubsiteHomeR :: YesodAuth master =>
HandlerT Subsite (HandlerT master IO) Html
at src/Subsite.hs:24:1-89
* In the second argument of `($)', namely `print maid'
In a stmt of a 'do' block: liftIO $ print maid
In the second argument of `($)', namely
`do { maid <- maybeAuthId;
liftIO $ print maid;
defaultLayout
(do { (asWidgetT . toWidget)
((blaze-markup-0.8.0.0:Text.Blaze.Internal.preEscapedText . T.pack)
"<p>Welcome to the Subsite!</br><a href=\"");
(getUrlRenderParams
>>=
(\ urender_alJ6
-> (asWidgetT . toWidget)
(toHtml
((\ u_alJ7 -> urender_alJ6 u_alJ7 ...)
(toParent SubsiteHomeR)))));
(asWidgetT . toWidget)
((blaze-markup-0.8.0.0:Text.Blaze.Internal.preEscapedText . T.pack)
"\">Subsite</br></a>\n\
\<a href=\"");
.... }) }'
Seems to me like all you need is a Show (AuthId master) constraint in your type signature:
getSubsiteHomeR :: (YesodAuth master, Show (AuthId master)) => HandlerT Subsite (HandlerT master IO) Html
Note that this requires the FlexibleContexts language extension, which you can enable by putting {-# LANGUAGE FlexibleContexts #-} at the top of your source file.

In Yesod.Test, is it possible to get into `SpecM (TestApp site)` from `IO`, similarly to how `liftIO` allows the opposite?

More specifically, I'm looking for a function of type:
f :: site -> SpecM (TestApp site) b -> IO b
or similar.
In other words, I am looking for the inverse function of:
liftIO :: IO b -> SpecM (TestApp _site) b
I've looked in the Yesod.Test docs and source code and tried to construct it myself too, but I don't seem to find it anywhere and it doesn't seem to be simple to construct either. Have I overlooked something, and might it exist somewhere? Or could it be simple to construct in a way I have not considered?
Background
I've run into the need for such a function multiple times, but so far I've always been able to workaround or do without. But in my latest situation it doesn't seem possible to work around: I want to write a Monadic QuickCheck property that would set up an arbitrary db environment (runDB . insert) and run some handlers on it to test for certain invariants of my application.
This is the farthest I've gotten with my attempts:
propSpec = do
context "qt" $ do
it "monadic" $ property $ \a b -> monadicIO $ do
run $ withServer $ \site -> yesodSpec site $ runDB $ insert $ User{..}
assert $ a /= b
withServer cont = do
p <- runLogging $ withSqlitePool "database_test.db3" 10 $ \pool -> do
runSqlPool (runMigration migrateAll) pool
return pool
wipeDB (MyApp p)
cont (MyApp p, id)
Which, somewhat understandably, results in the following type error:
/path/to/project/spec/Spec.hs:301:35: error:
• Couldn't match type ‘hspec-core-2.2.4:Test.Hspec.Core.Spec.Monad.SpecM
() ()’
with ‘IO a0’
Expected type: IO a0
Actual type: Spec
• In the expression:
yesodSpec site $ runDB $ insert $ User{..}
In the second argument of ‘($)’, namely
‘\ site
-> yesodSpec site $ runDB $ insert $ User{..}’
In the second argument of ‘($)’, namely
‘withServer
$ \ site
-> yesodSpec site $ runDB $ insert $ User{..}’
/path/to/project/spec/Spec.hs:301:52: error:
• Couldn't match type ‘ST.StateT
(YesodExampleData MyApp) IO (Key User)’
with ‘transformers-0.5.2.0:Control.Monad.Trans.Writer.Lazy.WriterT
[YesodSpecTree (MyApp, a1 -> a1)]
Data.Functor.Identity.Identity
()’
Expected type: YesodSpec (MyApp, a1 -> a1)
Actual type: YesodExample MyApp (Key User)
• In the second argument of ‘($)’, namely
‘runDB $ insert $ User{..}’
In the expression:
yesodSpec site $ runDB $ insert $ User{..}
In the second argument of ‘($)’, namely
‘\ site
-> yesodSpec site $ runDB $ insert $ User{..}’
• Relevant bindings include
site :: (MyApp, a1 -> a1)
(bound at /path/to/project/spec/Spec.hs:301:27)
Any ideas?
Also, has anyone before used Test.QuickCheck.Monadic successfully in conjunction with Yesod.Test? Or if perhaps not successfully, at least attempted to do so?

Run Persistent queries inside a module

Keeping in mind that I never succeded in understanding what a monad is, I'm having a problem with Yesod. I have a form that I would need to use in multiple Handlers, so I'm trying to define it in a module that I could import when needed. That form is using a bunch of not exported functions to create custom Fields.
When trying to run simple database operation like I'm used to inside my Handlers (trying to get default values for the fields), I get the following error :
Couldn't match expected type ‘Maybe (Entity Client)’
with actual type ‘HandlerT site0 IO (Maybe (Entity Client))’
In a stmt of a 'do' block: runDB $ selectFirst [ClientId ==. id] []
In the expression: do { runDB $ selectFirst [ClientId ==. id] [] }
In a case alternative:
Just id -> do { runDB $ selectFirst [ClientId ==. id] [] }
I tried a bunch of stuff, using <- doesn't seem to change anything. I think that I might not be inside the correct monad ? I googled a lot and as I understand it, runDB will only work inside the Handler monad, but I have no idea how to get in that, if at all possible.
I have multiple Handlers importing that module, calling that function to generate an AForm, if that matters.
Thanks a lot !
EDIT :
Here is the function raising that error :
getClientFromId :: Maybe (Key Client) -> Maybe (Entity Client)
getClientFromId mId = do
case mId of
Nothing -> Nothing
Just id -> do
runDB $ selectFirst [ClientId ==. id] []
Update
So after reading the answers, I tried removing the signature : no change.
I tried using the different suggested signatures but nothing worked. Here is what I have currently :
getClientFromId :: Maybe (Key Client) -> Handler (Maybe (Entity Client))
getClientFromId mId = do
case mId of
Nothing -> return Nothing
Just id -> do
client <- runDB $ selectFirst [ClientId ==. id] []
return client
I'm calling that from a ternary inside the definition of an AForm :
<*> (fmap entityKey <$> aopt clientIdField (addIdToField "ClientIdField" (bfs ("Owner" :: Text))) (isJust mHorse ? (Just (getClientFromId $ horseClient $ fromJust mHorse)) :? Nothing))
The ternary isn't very easy to read, if I figure out how to make all of this work I'll probably change that :).
I get that error :
Couldn't match type ‘HandlerT App IO (Maybe (Entity Client))’
with ‘Maybe (Entity Client)’
Expected type: Maybe (Entity Client)
Actual type: Handler (Maybe (Entity Client))
In the first argument of ‘Just’, namely
‘(getClientFromId $ horseClient $ fromJust mHorse)’
In the first argument of ‘(:?)’, namely
‘(Just (getClientFromId $ horseClient $ fromJust mHorse))’
In the second argument of ‘(?)’, namely
‘(Just (getClientFromId $ horseClient $ fromJust mHorse))
:? Nothing’
Looking at that, it seems that I have the correct return types except there is a "HandlerT site0 IO" on top of it. I tried using an intermediary function with a <- to remove it but that's not working. I'm guessing my actual problem here is that I don't understand monads, but I looked at other Yesod projects that work and I really don't get what I'm doing differently.
(I know that's nesting multiple Maybes, but in my DB horseClient is a maybe, and the default value of a form is waiting for a Maybe, so it seems "normal" that the final value should be a Maybe (Maybe (Entity Client)) even if that's a bit strange)
You declared getClientId as a pure function, but you are also calling runDB which performs I/O, so it can't be a pure function. Intuitively that's why it doesn't type check.
I suggest that you write getClientId in isolation without a type signature and see what GHC infers. My guess is that it will be something like:
Maybe (Key Client) -> Handler (Maybe (Entity Client))
That means everywhere you call it you'll need to do something like:
mid' <- getClientFromId mid
Let's look at the types.
selectFirst :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m) =>
[Filter val] ->
[SelectOpt val] ->
m (Maybe (Entity val))
runDB :: YesodDB site a -> HandlerT site IO a
type YesodDB site = ReaderT (YesodPersistBackend site) (HandlerT site IO)
The m in your selectFirst will be YesodDB site which is an alias for a ReaderT that I'm guessing provides the database connection.
I think the correct type for your function should be
Maybe (Key Client) -> HandlerT site IO (Maybe (Entity Client))
site will be replaced with your Yesod instance type.
update:
First of all, this is pretty ugly:
(isJust mHorse ? (Just (getClientFromId $ horseClient $ fromJust mHorse)) :? Nothing)
You could do this instead:
fmap (justClientFromId . horseClient) mHorse
However, that brings us to the second problem. justClientFromid returns a Handler wrapping the value you want.
I think the main problem stems from your first statement:
Keeping in mind that I never succeded in understanding what a monad is
You should probably read the Yesod documentation and try to understand what's going on.

Lazy bytestring insanity

I'm really struggling to square this circle.
getPostContent uses Wreq to download a blog post and return it.
getPostContent url = do
let opts = defaults & W.checkStatus .~ (Just $ \_ _ _ -> Nothing)
postResp <- getWith opts $ baseUrl ++ url
if postResp ^. W.responseStatus . statusCode == 200
-- then return $ LEnc.encodeUtf8 $ postResp ^. W.responseBody . _String -- :: Prism T Text
then return $ postResp ^. W.responseBody . _String
else return "error downloading"
This is consumed by parseLBS
do
page <- getPostContent r -- :: IO String
let
-- parseLBS :: Data.ByteString.Lazy.Internal.ByteString -> Text.XML.Document
cursor = fromDocument $ parseLBS page
As I understand it, getPostContent is providing Data.Text.Text, whereas I need Data.ByteString.Lazy.Internal.ByteString and I cannot work out how to convert them ( thought it should be this, see code snippet above, but it does not compile either).
Couldn't match expected type ‘Data.ByteString.Lazy.Internal.ByteString’
with actual type ‘T.Text’
In the first argument of ‘parseLBS’, namely ‘page’
In the second argument of ‘($)’, namely ‘parseLBS page’
Compilation message with encode uncommented
Couldn't match type ‘TL.Text’
with ‘T.Text’
NB: ‘TL.Text’ is defined in ‘Data.Text.Internal.Lazy’
‘T.Text’ is defined in ‘Data.Text.Internal’
Expected type: (TL.Text -> Const TL.Text TL.Text)
-> Data.ByteString.Lazy.Internal.ByteString
-> Const TL.Text Data.ByteString.Lazy.Internal.ByteString
Actual type: (T.Text -> Const TL.Text T.Text)
-> Data.ByteString.Lazy.Internal.ByteString
-> Const TL.Text Data.ByteString.Lazy.Internal.ByteString
In the second argument of ‘(.)’, namely ‘_String’
In the second argument of ‘(^.)’, namely ‘responseBody . _String’
To summarise: encodeUtf8 is the right way to go. It seems the one you're using is from Data.Text.Lazy.Encoding, which requires a lazy Text. You can use Data.Text.Lazy.fromStrict to convert... Or you can look into Data.Text.Encoding, which works on strict Text (but then gives you a strict ByteString...)

Resources