Reputation: 2329
How can I handle regular form POSTs with Servant? In particular, given an HTML form like
<form action="/check" method="post">
Solution:
<input name="code" type="text">
<input type="submit">
</form>
and
data CheckResult = Correct | Wrong
instance ToHtml CheckResult
...
checkCode :: Text -> Handler CheckResult
checkCode code = if code == "secret" then Correct else Wrong
how do I string things together?
Upvotes: 9
Views: 1524
Reputation: 7845
I just wanted to add an answer for a recent version of Servant, because I had to google various things in order to assemble a complete, working version of form handling.
The above answer works well for earlier versions of Servant, but I got stuck using forms when upgrading to Servant 0.9.
Here's how I did it.
First of all, they switched from a custom Form implementation to the one in http-api-data, so you need that in your cabal file:
some-project.cabal
build-depends: base >= 4.7 && < 5
, aeson
, blaze-html
, http-api-data
Next, you can declare a form, like above, but you can use GHC.Generics
to automatically derive a FromForm
instance:
{-# LANGUAGE DeriveGeneric #-}
module Html.Contact where
import GHC.Generics
import Servant
import Web.FormUrlEncoded (FromForm)
data ContactForm = ContactForm
{ cname :: !T.Text
, cemail :: !T.Text
, cmessage :: !T.Text
} deriving (Eq, Show, Generic)
instance FromForm ContactForm
After that, you can use the regular FormUrlEncoded
ContentType from Servant in your endpoint:
type ContactApi = "contact" :> ReqBody '[FormUrlEncoded] ContactForm
:> Post '[HTML] Html
Almost forgot: How to render the thing
You will also need a page, probably, where you are displaying your form? Well, the "name" attributes have to match the fields in your form (here's how I did it, using Blaze
):
contactForm :: H.Html
contactForm = H.section ! A.id "contact" ! A.class_ "container contact-us u-full-width u-max-full-width" $
H.div ! A.class_ "row" $ do
H.div ! A.class_ "eight columns contact-us-form" $
H.form ! A.method "post" ! A.action "/contact" $ do
H.div ! A.class_ "row" $ do
H.div ! A.class_ "six columns" $
H.input ! A.class_ "u-full-width" ! A.type_ "text" ! A.name "cname" ! A.placeholder "Name" ! A.id "nameInput"
H.div ! A.class_ "six columns" $
H.input ! A.class_ "u-full-width" ! A.type_ "text" ! A.name "cemail" ! A.placeholder "Email" ! A.id "emailInput"
H.textarea ! A.class_ "u-full-width" ! A.name "cmessage" ! A.placeholder "Message" ! A.id "messageInput" $ ""
H.input ! A.class_ "button u-pull-right" ! A.type_ "submit" ! A.value "Send"
Upvotes: 12
Reputation: 2329
Servant supports this via the data type FormUrlEncoded, and the class FromFormUrlEncoded (renamed to FromForm
in Servant 0.9).
First we define a data type for the form data, and rewrite our handler to accept that.
data CheckRequest = CheckRequest { code :: Text }
checkCode :: CheckRequest -> Handler CheckResult
checkCode (CheckRequest code) = if code == "secret" then Correct else Wrong
Then we specify a POST body of type application/x-www-form-urlencoded
.
type API = "check"
:> ReqBody '[FormUrlEncoded] CheckRequest
:> Post '[HTML] CheckResult
Now all that's required is to make CheckRequest
an instance of FromFormUrlEncoded
.
instance FromFormUrlEncoded CheckRequest where
--fromFormUrlEncoded :: [(Text, Text)] -> Either String CheckRequest
fromFormUrlEncoded [("code", c)] = Right (CheckRequest c)
fromFormUrlEncoded _ = Left "expected a single field `code`"
Upvotes: 4