This is a follow-up to my question here: Extracting database field values inside a Handler
I would like to extract some information from the database and pass it in as a label value for a form. However, I get a type error.
A simple demonstration below (shell code from the Yesod Book):
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative ((<$>), (<*>))
import Data.Text (Text)
import Data.Time (Day)
import Yesod
import Yesod.Form.Jquery
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
instance YesodJquery App
data Person = Person
{ personName :: Text
}
deriving Show
personForm :: Text -> Html -> MForm Handler (FormResult Person, Widget)
personForm n1 = renderDivs $ Person
<$> areq textField n1 Nothing -- Changing n1 to "Name" works just fine.
getHomeR :: Handler Html
getHomeR = do
(widget, enctype) <- generateFormPost $ personForm "test"
defaultLayout
[whamlet|
<p>
The widget generated contains only the contents
of the form, not the form tag itself. So...
|]
main :: IO ()
main = warp 3000 App
When I run the program with runhaskell, I get the following error:
Couldn't match expected type `FieldSettings site0'
with actual type `Text'
In the second argument of `areq', namely `n1'
In the second argument of `(<$>)', namely
`areq textField n1 Nothing'
In the second argument of `($)', namely
`Person <$> areq textField n1 Nothing'
I also tried (FieldSettings n1 Nothing Nothing Nothing []) but no luck. Any thoughts on how to pass in label values to areq?
Let us look at the type of areq
areq :: (RenderMessage master msg, RenderMessage master FormMessage) => Field sub master a -> FieldSettings msg -> Maybe a -> AForm sub master a
So the areq function doesn't expect a Text value as its second parameter, it expects a FieldSettings msg. So what is the reason that it works when you write "Name" in your source code?
Looking up the documentation for FieldSettings in the documentation for yesod-forms we see that it is a datatype that has an IsString instance. Looking at the specific instance in the source code
we see that:
instance (a ~ Text) => IsString (FieldSettings a) where
fromString s = FieldSettings (fromString s) Nothing Nothing Nothing []
So everytime you write a String in your source code (if the OverloadedStrings extension is enabled), the compiler will insert the contents of the fromString instance.
However, you don't want to enter a String, but instead you want to create a FieldSettings from a Text value. Looking further we see that the first part of a FieldSettings is a SomeMessage, checking the documentation yet again and the source we see that a SomeMessage can be created by using the SomeMessage constructor.
personForm :: Text -> Html -> MForm Handler (FormResult Person, Widget)
personForm n1 = renderDivs $ Person
<$> areq textField (FieldSettings (SomeMessage n1) Nothing Nothing Nothing []) Nothing
Related
Supposing I'm running a Servant webserver, with two endpoints, with a type looking like this:
type BookAPI =
"books" :> Get '[JSON] (Map Text Text)
:<|> "book" :> Capture "Name" Text :> ReqBody '[JSON] (Text) :> Post '[JSON] (Text)
λ:T.putStrLn $ layout (Proxy :: Proxy BookAPI)
/
├─ book/
│ └─ <capture>/
│ └─•
└─ books/
└─•
I might want to use something like Network.Wai.Middleware.Prometheus's instrumentHandlerValue to generate a Prometheus metric that fire's every time this API is called, with a handler value set to the path of the request.
However, if I do something like the following:
prometheusMiddlware = instrumentHandlerValue (T.intercalate "\\" . pathInfo)
This is bad, because different requests to the book/<Name> endpoint, such as book/great-expectations and book/vanity-fair result in different labels, this is fine if the number of books is small, but if it's very large then the amount of data used by these metrics is very big, and either my service falls over, or my monitoring bill becomes very large.
I'd quite like a function, that took a Servant API, and a Wai Request, and if it matched, returned a list of segments in a form that was the same for each endpoint.
That is requests to /books would return Just ["books"], requests to /book/little-dorrit would return Just ["book", "Name"], and requests to /films would return Nothing.
I can kind of see how you might go about writing this by pattern matching on Router' from Servant.Server.Internal.Router, but it's not clear to me that relying on an internal package in order to do this is a good idea.
Is there a better way?
The pathInfo function returns all the path segments for a Request. Perhaps we could define a typeclass that, given a Servant API, produced a "parser" for the list of segments, whose result would be a formatted version of the list.
The parser type could be something like:
import Data.Text
import Control.Monad.State.Strict
import Control.Applicative
type PathParser = StateT ([Text],[Text]) Maybe ()
Where the first [Text] in the state are the path segments yet to be parsed, and the second are the formatted path segments we have accumulated so far.
This type has an Alternative instance where failure discards state (basically backtracking) and a MonadFail instance that returns mzero on pattern-match failure inside do-blocks.
The typeclass:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Data ( Proxy )
import GHC.TypeLits
class HasPathParser (x :: k) where
pathParser :: Proxy x -> PathParser
The instance for Symbol moves the path piece from the pending list to the processed list:
instance KnownSymbol piece => HasPathParser (piece :: Symbol) where
pathParser _ = do
(piece : rest, found) <- get -- we are using MonadFail here
guard (piece == Data.Text.pack (symbolVal (Proxy #piece)))
put (rest, piece : found)
The instance for Capture puts the name of the path variable—not the value—on the processed list:
instance KnownSymbol name => HasPathParser (Capture name x) where
pathParser _ = do
(_ : rest, found) <- get -- we are using MonadFail here
put (rest, Data.Text.pack (symbolVal (Proxy #name)) : found)
When we reach a Verb (GET, POST...) we require that no pending path pieces should remain:
instance HasPathParser (Verb method statusCode contextTypes a) where
pathParser _ = do
([], found) <- get -- we are using MonadFail here
put ([], found)
Some other instances:
instance HasPathParser (ReqBody x y) where
pathParser _ = pure ()
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :> b) where
pathParser _ = pathParser (Proxy #a) *> pathParser (Proxy #b)
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :<|> b) where
pathParser _ = pathParser (Proxy #a) <|> pathParser (Proxy #b)
Putting it to work:
main :: IO ()
main = do
do let Just ([], result) = execStateT (pathParser (Proxy #BookAPI)) (["books"],[])
print result
-- ["books"]
do let Just ([], result) = execStateT (pathParser (Proxy #BookAPI)) (["book", "somebookid"],[])
print result
-- ["Name","book"]
The code below is from the Home.hs file created by the yesod-simple scaffold.
I like to do simple string manipulation on text input but don't know how to parse it into a Text value.
How, for example, can I use toUpper on fileDescription?
I've tried using lookupPostParam
but I'm struggling with it's type signature:
lookupPostParam :: MonadHandler m => Text -> m (Maybe Text)
Home.hs
module Handler.Home where
import Import
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3)
import Text.Julius (RawJS (..))
data FileForm = FileForm
{ fileInfo :: FileInfo
, fileDescription :: Text
}
getHomeR :: Handler Html
getHomeR = do
(formWidget, formEnctype) <- generateFormPost sampleForm
let submission = Nothing :: Maybe FileForm
handlerName = "getHomeR" :: Text
defaultLayout $ do
let (commentFormId, commentTextareaId, commentListId) = commentIds
aDomId <- newIdent
setTitle "Welcome To Yesod!"
$(widgetFile "homepage")
postHomeR :: Handler Html
postHomeR = do
((result, formWidget), formEnctype) <- runFormPost sampleForm
let handlerName = "postHomeR" :: Text
submission = case result of
FormSuccess res -> Just res
_ -> Nothing
defaultLayout $ do
let (commentFormId, commentTextareaId, commentListId) = commentIds
aDomId <- newIdent
setTitle "Welcome To Yesod!"
$(widgetFile "homepage")
sampleForm :: Form FileForm
sampleForm = renderBootstrap3 BootstrapBasicForm $ FileForm
<$> fileAFormReq "Choose a file"
<*> areq textField textSettings Nothing
where textSettings = FieldSettings
{ fsLabel = "What's on the file?"
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs =
[ ("class", "form-control")
, ("placeholder", "File description")
]
}
commentIds :: (Text, Text, Text)
commentIds = ("js-commentForm", "js-createCommentTextarea", "js-
commentList")
This is unfortunately a fault in documentation and communication.
Given
lookupPostParam :: (MonadResource m, MonadHandler m) => Text -> m (Maybe Text)
the reader is meant to infer that m is not only a MonadResouce and a MonadHandler but also Monad. This tiny little line of code packs up a lot of intent into a very small sentence; it's a wart that so much of Haskell library usage is left implicit and subtextual. For example, to call toUpper on the Text inside this type you are meant to do this:
{-# language OverloadedStrings #-}
foo :: (MonadResource m, MonadHandler m) => m (Maybe Text)
foo = do
valueMaybe <- lookupPostParam "key"
case valueMaybe of
Just value ->
pure (toUpper value)
Nothing ->
Nothing
Note that the monad stack (MonadHandler, MonadResource) has "infected" your code. This is meant to be intentional, so as to constrain you via the type checker to only run this function in the intended Yesod environment/state machine/context/whatever.
However
You are using yesod-forms and it would be nice to do the same thing within that framework. As with lookupPostParam, we can take advantage of the monad-applicative-functor typeclasses.
We can adapt this to the Form FileForm value that you have.
sampleForm :: AForm Handler FileForm
sampleForm =
FileForm <$> fileAFormReq "Choose a file"
<*> (toUpper <$> areq textField textSettings Nothing)
I think the types of yesod-forms changed between releases. I'm copying my types off the latest version as of writing, 1.4.11.
Here we take advantage of the Monad m => Functor (AForm m) instance. Knowing that we are indeed in a monad (the Handler monad) means we can use fmap and its infixed sibling <$> on the value returned by areq textField textSettings Nothing. This allows us to lift arbitrary functions acting on Text into the AForm m stack. For example, here we went from Text -> Text to AForm Handler Text -> AForm Handler Text.
Hope that helps.
Let's say we have something like this:
myForm :: Form (Text, Text)
myForm = renderBootstrap3 BootstrapBasicForm $ (,)
<$> areq passwordField (bfs ("Password" :: Text)) Nothing
<*> areq passwordField (bfs ("Repeat password" :: Text)) Nothing
Is it possible to check whether the both field are the same? Validation is
described here,
check
seems to be not powerful enough to perform this sort of check. Maybe
checkM
may be of some use?
If it's not possible to do with built-in Yesod facilities, what would be the
best work-around? I can think of:
postSomethingR :: Handler Html
postSomethingR = do
((result, form), enctype) <- runFormPost myForm
case result of
FormSuccess (password0, password1) -> do
if password0 == password1
then
-- do your thing
else
-- serve the form again and perhaps set message telling that
-- passwords don't match?
This is a working example of a custom password field that checks if the input from both boxes were the same. This comparison is created in the record fieldParse.
To run this example from cmd: stack runghc <filename.hs>
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative
import Data.Text (Text)
import Yesod
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
passwordConfirmField :: Field Handler Text
passwordConfirmField = Field
{ fieldParse = \rawVals _fileVals ->
case rawVals of
[a, b]
| a == b -> return $ Right $ Just a
| otherwise -> return $ Left "Passwords don't match"
[] -> return $ Right Nothing
_ -> return $ Left "You must enter two values"
, fieldView = \idAttr nameAttr otherAttrs eResult isReq ->
[whamlet|
<input id=#{idAttr} name=#{nameAttr} *{otherAttrs} type=password>
<div>Confirm:
<input id=#{idAttr}-confirm name=#{nameAttr} *{otherAttrs} type=password>
|]
, fieldEnctype = UrlEncoded
}
getHomeR :: Handler Html
getHomeR = do
((res, widget), enctype) <- runFormGet $ renderDivs $
areq passwordConfirmField "Password" Nothing
defaultLayout
[whamlet|
<p>Result: #{show res}
<form enctype=#{enctype}>
^{widget}
<input type=submit value="Change password">
|]
main :: IO ()
main = warp 3000 App
Let message/en.msg file like:
Category1: some text 1
Category2: some text 2
...
CategoryN: some text N
And let next valid code:
getHomeR :: Handler RepHtml
getHomeR = do
(msg :: AppMessage -> Text) <- getMessageRender
let list = T.concat $ map msg [MsgCategory1, MsgCategory7]
defaultLayout $ do
$(widgetFile "homepage") -- <p>List: #{list}
then, list contains MsgCategory1 and MsgCategory7 translations.
I want to do some like:
let list = T.concat $ map msg [MsgCategory1 .. MsgCategory7]
but AppMessage is not Enum derived.
My another one alternative is not valid too (is not Read derived too)
let list = T.concat $ map (\n -> msg $ read "MsgCategory" ++ show n) [1 .. 7]
In general, how to cast "in real time" AppMessage elements?
Thank you very much!
(I love Yesod! :) )
One way I found is here
deriving instance Enum AppMessage
deriving instance Eq AppMessage
deriving instance Read AppMessage
deriving instance Show AppMessage
...
(require -XStandaloneDeriving)
Explained:
Change on Foundation.hs:
-- Set up i18n messages. See the message folder.
mkMessage "App" "messages" "en"
-- Require extension: StandaloneDeriving
deriving instance Enum AppMessage
On site.cabal:
extensions: TemplateHaskell
QuasiQuotes
OverloadedStrings
NoImplicitPrelude
CPP
MultiParamTypeClasses
TypeFamilies
GADTs
GeneralizedNewtypeDeriving
FlexibleContexts
EmptyDataDecls
NoMonomorphismRestriction
StandaloneDeriving
I am implementing a "contact me" form that will send an email when it is submitted. I needed this form to emit custom HTML, so I ended up using monadic forms. The problem is that I do not know how to use a monadic form.
the code is below. I have omitted the part that sends e-mail for brevity. the problem is that my form never validates correctly. the form result is never FormSuccess in my postContactR function.
It seems that I do not initialize the form correctly when I call runFormPost inside postContactR. I always pass Nothing instead of the actual ContactData to contactForm and I do not know how to construct my ContactData from the request. Is my understanding of the problem correct? I am trying to work with poorly documented features. :)
any help?
EDIT: what looks strange is that validation errors do show up in the form if I submit an invalid form, so the request data does get read at some point. what does not work is that when there are no errors I do not get redirected to RootR
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Handler.Contact where
import Control.Applicative ((<$>), (<*>))
import Data.Text (Text)
import Foundation
import Network.Mail.Mime
data ContactData = ContactData
{ contactName :: Text
, contactEmail :: Text
, contactMessage :: Textarea
}
deriving Show
contactForm d = \html -> do
(r1, v1) <- mreq textField "Your name:" (contactName <$> d)
(r2, v2) <- mreq emailField "Your e-mail:" (contactEmail <$> d)
(r3, v3) <- mreq textareaField "Message:" (contactMessage <$> d)
let views = [v1, v2, v3]
return (ContactData <$> r1 <*> r2 <*> r3, $(widgetFile "contact-form"))
getContactR :: Handler RepHtml
getContactR = do
((_, form), _) <- runFormPost (contactForm Nothing)
defaultLayout $ do
setTitle "contact"
addWidget $(widgetFile "contact")
postContactR :: Handler RepHtml
postContactR = do
((r, form), _) <- runFormPost (contactForm Nothing)
case r of
FormSuccess d -> do
sendEmail d
setMessage "Message sent"
redirect RedirectTemporary RootR
_ -> getContactR
Are you including the html value in contact-form.hamlet? It's a nonce value. You'd get better debug information if you printed the value of r (in postContactR).
I have on my writing TODO list to add a monadic form example, it should be up soon.