Form
processing using reform
reform
is a library for creating type-safe, composable, and
validated HTML forms. It is built around applicative functors and is
based on the same principles as formlets
and digestive-functors <=
0.2
.
The core reform
library is designed to be portable and can be used
with a wide variety of Haskell web frameworks and template solutions
-- though only a few options are supported at the moment.
The most basic method of creating and processing forms with out the
assistance of reform
is to:
create a <form>
tag with the desired elements by hand
write code which processes the form data set and tries to extract a value from it
The developer will encounter a number of difficulties using this method:
the developer must be careful to use the same name
field in the
HTML and the code.
if a new field is added to the form, the code must be manually updated. Failure to do so will result in the new field being silently ignored.
form fragments can not be easily combined because the name
or
id
fields might collide. Additionally, there is no simple way to
combine the validation/value extraction code.
if the form fails to validate, it is difficult to redisplay the form with the error messages and data that was submitted.
reform
solves these problems by combining the view generation code
and validation code into a single Form
element. The Form
elements
can be safely combined to create more complex forms.
In theory, reform
could be applied to other domains, such as
command-line or GUI applications. However, reform
is based around
the pattern of:
For most interactive applications, there is no reason to wait until the entire form has been filled out to perform validation.
reform
is an extension of the OCaml-based formlets
concept
originally developed by Ezra Cooper, Sam Lindley, Philip Wadler and
Jeremy Yallop. The original formlets
code was ported to Haskell as the
formlets
library, and then revamped again as the
digestive-functors <= 0.2
library.
digestive-functors
0.3 represents a major break from the traditional
formlets
model. The primary motivation behind digestive-functors
0.3 was (mostly likely) to allow the separation of validators from the
view code. This allows library authors to define validation for forms,
while allowing the library users to create the view for the forms. It also
provides a mechanism to support templating systems like Heist
, where
the view is defined in an external XML file rather than Haskell code.
In order to achieve this, digestive-functors
0.3 unlinks the
validation and view code and requires the developers to stitch them
back together using String
based names. This, of course, leads to
runtime errors. If the library author adds new required fields to the
validator, the user gets no compile time warnings or errors to let
them know their code is broken.
The Reform
library is a heavily modified fork of
digestive-functors
0.2. It builds on the the traditional formlets
safety and style and extends it to allow view and validation
separation in a type-safe manner.
You can find the original papers on formlets
here.
Form
!You will need to install the following optional packages for this section:
cabal install reform reform-happstack reform-hsp
The easiest way to learn Reform
is through example. We will start
with a simple form that does not require any special validation. We
will then extend the form, adding some simple validators. And then we
will show how we can split the validation and view for our form into
separate libraries.
This example uses Happstack for the web server and HSP for the templating library.
First we have some pragmas:
> {-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses > , ScopedTypeVariables, TypeFamilies, TypeSynonymInstances #-} > {-# OPTIONS_GHC -F -pgmFtrhsx #-} > module Main where >
And then some imports. We import modules from three different reform
packages: the core reform
library, the reform-happstack
package, and the reform-hsp
package:
> import Control.Applicative > import Control.Applicative.Indexed (IndexedFunctor(..), IndexedApplicative(..)) > import Control.Monad (msum) > import Happstack.Server > import Happstack.Server.HSP.HTML () > import HSP.ServerPartT > import HSP > import Text.Reform ( CommonFormError(..), Form, FormError(..), Proof(..), (++>) > , (<++), commonFormErrorStr, decimal, prove > , transformEither, transform ) > import Text.Reform.Happstack > import Text.Reform.HSP.String >
Next we will create a type alias for our application's server monad:
> type AppT m = XMLGenT (ServerPartT m) >
We will also want a function that generates a page template for our app:
> appTemplate :: ( Functor m, Monad m > , EmbedAsChild (ServerPartT m) headers > , EmbedAsChild (ServerPartT m) body > ) => > String -- ^ contents of <title> tag > -> headers -- ^ extra content for <head> tag, use () for nothing > -> body -- ^ contents of <body> tag > -> AppT m Response > appTemplate title headers body = > toResponse <$> > <html> > <head> > <title><% title %></title> > <% headers %> > </head> > <body> > <% body %> > </body> > </html> >
Forms have the type Form
which looks like:
> newtype Form m input error view proof a = Form { ... }
As you will note it is heavily parameterized:
m
input
error
view
proof
a
In order to keep our type signatures sane, it is convenient to create an application specific type alias for the Form
type:
> type SimpleForm = Form (AppT IO) [Input] AppError [AppT IO (XMLType (ServerPartT IO))] () >
AppError
is an application specific type used to report form validation errors:
> data AppError > = Required > | NotANatural String > | AppCFE (CommonFormError [Input]) > deriving Show >
Instead of have one error type for all the forms, we could have per-form error types -- or even just use String
. The advantage of using a type is that it makes it easier to provide I18N translations, or for users of a library to customize the text of the error messages. The disadvantage of using a custom type over a plain String
is that it can make it more difficult to combine forms into larger forms since they must all have the same error type. Additionally, it is a bit more work to create the error type and the FormError
instance.
We will want an EmbedAsChild
instance so that we can easily embed the errors in our HTML:
> instance (Monad m) => EmbedAsChild (ServerPartT m) AppError where > asChild Required = asChild $ "required" > asChild (NotANatural str) = asChild $ "Could not decode as a positive integer: " ++ > str > asChild (AppCFE cfe) = asChild $ commonFormErrorStr show cfe >
The error type also needs a FormError
instance:
> instance FormError AppError where > type ErrorInputType AppError = [Input] > commonFormError = AppCFE >
Internally, reform
has an error type CommonFormError
which is used
to report things like missing fields and other internal errors. The
FormError
class is used to lift those errors into our custom error
type.
Now we have the groundwork laid to create a simple form. Let's create a form that allows users to post a message. First we will want a type to represent the message -- a simple record will do:
> data Message = Message > { name :: String -- ^ the author's name > , title :: String -- ^ the message title > , message :: String -- ^ contents of the message > } deriving (Eq, Ord, Read, Show) >
and a simple function to render the Message
as XML
:
> renderMessage :: (Monad m) => Message -> AppT m XML > renderMessage msg = > <dl> > <dt>name:</dt> <dd><% name msg %></dd> > <dt>title:</dt> <dd><% title msg %></dd> > <dt>message:</dt> <dd><% message msg %></dd> > </dl> >
Now we can create a very basic form:
> postForm :: SimpleForm Message > postForm = > Message > <$> label "name:" ++> inputText "" <++ br > <*> label "title: " ++> inputText "" <++ br > <*> (label "message:" <++ br) ++> textarea 80 40 "" <++ br > <* inputSubmit "post" >
This form contains all the information needed to generate the form elements and to parse the submitted form data set and extract a Message
value.
The following functions come from reform-hsp
. reform-blaze
provides similar functions.
label
function creates a <label>
element using the supplied label.
inputText
function creates a <input type="text">
input element using the argument as the initial value.
inputSubmit
function creates a <input type="submit">
using the argument as the value.
textarea
function creates <textearea>
. The arguments are the number of cols, rows, and initial contents.
br
functions creates a Form
element that doesn't do anything except insert a <br>
tag.
The <$>
, <*>
and <*
operators come from Control.Applicative
. If you are not familiar with applicative functors then you will want to read a tutorial such as this one.
++>
comes from the reform
library and has the type:
> (++>) :: (Monad m, Monoid view) => > Form m input error view () () > -> Form m input error view proof a > -> Form m input error view proof a
The ++>
operator is similar to the *>
operator with one important difference. If we were to write:
> label "name: " *> inputText
then the label
and inputText
would each have unique FormId
values. But when we write:
> label "name: " ++> inputText
they have the same FormId
value. The FormId
value is typically used to create unique name
and id
attributes for the form elements. But, in the case of label
, we want the for
attribute to refer to the id
of the element it is labeling. There is also a similar operator <++ for when you want the label after the element.
We also use <++
and ++>
to attach error messages to form elements.
Form
The easiest way to use Form
is with the happstackEitherForm
function:
> postPage :: AppT IO Response > postPage = > dir "post" $ > do result <- happstackEitherForm (form "/post") "post" postForm > case result of > (Left formHtml) -> appTemplate "post" () formHtml > (Right msg) -> appTemplate "Your Message" () $ renderMessage msg >
happstackEitherForm
has the type:
> happstackEitherForm :: (Happstack m) => > ([(String, String)] -> view -> view) -- ^ wrap raw form html > -- inside a <form> tag > -> String -- ^ form prefix > -> Form m [Input] error view proof a -- ^ Form to run > -> m (Either view a) -- ^ Result
For a GET
request, happstackEitherForm
will view the form with NoEnvironment
. It will always return Left view
.
For a POST
request, happstackEitherForm
will attempt to validate the form using the form submission data. If successful, it will return Right a
. If unsuccessful, it will return Left view
. In this case, the view will include the previously submitted data plus any error messages.
Note that since happstackEitherForm
is intended to handle both GET
and POST
requests, it is important that you do not have any method
calls guarding happstackEitherForm
that would interfere.
The first argument to happstackEitherForm
is a function what wraps the view inside a <form>
element. This function will typically be provided by template specific reform package. For example, reform-hsp
exports:
> -- | create <form action=action method="POST" enctype="multipart/form-data"> > form :: (XMLGenerator x, EmbedAsAttr x (Attr String action)) => > action -- ^ action url > -> [(String,String)] -- ^ extra hidden fields to add to form > -> [XMLGenT x (XMLType x)] -- ^ children > -> [XMLGenT x (XMLType x)]
The first argument to form
is the attribute to use for the action
attribute. The other arguments will be filled out by happstackEitherForm
.
The second argument to happstackEitherForm
is a unique String
. This is used to ensure that each <form>
on a page generates unique FormId
values. This is required since the FormId
is typically used to generate id
attributes, which must be unique.
The third argument to happstackEitherForm
is the the form we want to use.
reform
functionhappstackEitherForm
is fairly straight-forward, but can be a bit tedious at times:
case result of
is a bit tedious.HSP
, it is a bit annoying that the happstackEitherForm
appears outside of the rest of the page templateThese problems are even more annoying when a page contains multiple forms.
reform-happstack
exports reform
which can be used to embed a Form
directly inside an HSP
template:
> postPage2 :: AppT IO Response > postPage2 = > dir "post2" $ > appTemplate "post 2" () $ > <% reform (form "/post2") "post2" displayMessage Nothing postForm %> > where > displayMessage msg = appTemplate "Your Message" () $ renderMessage msg >
reform
has a pretty intense looking type signature but it is actually pretty straight-forward, and similar to eitherHappstackForm
:
> reform :: (ToMessage b, Happstack m, Alternative m, Monoid view) => > ([(String, String)] -> view -> view) -- ^ wrap raw form html inside > -- a @\<form\>@ tag > -> String -- ^ prefix > -> (a -> m b) -- ^ success handler used when > -- form validates > -> Maybe ([(FormRange, error)] -> view -> m b) -- ^ failure handler used when > -- form does not validate > -> Form m [Input] error view proof a -- ^ the formlet > -> m view > reform toForm prefix success failure form = ...
toForm
<form>
tag. Here we use the form
function from reform-happstack
. The first argument to form
is the action
url.prefix
FormId
prefix to use when rendering this form.handleSuccess
hHandleFailure
Nothing
then the form will simple by redisplayed in the original context.form
Form
to process.The happstackEitherForm
and reform
functions also have a hidden benefit -- they provide cross-site request forgery (CSRF) protection, using the double-submit method. When the <form>
is generated, the reform
or happstackEitherForm
function will create a secret token and add it to a hidden field in the form. It will also put the secret token in a cookie. When the user submits the form, the reform
function will check that the value in the cookie and the hidden field match. This prevents rogue sites from tricking users into submitting forms, because the rogue site can not get access to the secret token in the user's cookie.
That said, if your site is vulnerable to cross site script (XSS) attacks, then it may be possible for a remote site to steal the cookie value.
The form we have so far is very simple. It accepts any input, not caring if the fields are empty or not. It also does not try to convert the String
values to another type before adding them to the record.
However, we do still see a number of benefits. We specified the form once, and from that we automatically extract the code to generate HTML and the code to extract the values from the form data set. This adheres to the DRY (don't repeat yourself) principle. We did not have to explicitly name our fields, keep the names in-sync in two different places, worry if the HTML and processing code contain the same set of fields, or worry if a name/id has already been used. Additionally, we get automatic CSRF protection.
Form
with Simple ValidationThe next step is to perform some validation on the input fields. If the fields validate successfully, then we get a Message
. But if the input fails to validate, then we will automatically regenerate the Form
showing the data the user submitted plus validation errors.
For this example, let's simply make sure they entered something in all the fields. To do that we will create a simple validation function:
> required :: String -> Either AppError String > required [] = Left Required > required str = Right str >
In this case we are simply checking that the String
is not null. If it is null we return an error, otherwise we return the String
unmodified. Some validators will actually transform the value -- such as converting the String
to an Integer
.
To apply this validation function we can use transformEither
:
> transformEither :: Monad m => > Form m input error view anyProof a > -> (a -> Either error b) > -> Form m input error view () b
We can update our Form
to:
> validPostForm :: SimpleForm Message > validPostForm = > Message <$> name <*> title <*> msg <* inputSubmit "post" > where > name = errorList ++> label "name:" ++> > (inputText "" `transformEither` required) <++ br > > title = errorList ++> label "title:" ++> > (inputText "" `transformEither` required) <++ br > > msg = errorList ++> (label "message:" <++ br) ++> > (textarea 80 40 "" `transformEither` required) <++ br >
The errorList
will add a list of error messages to a Form
element. This gives greater control over where error messages appear
in the form. The list of errors is literally a list of errors inside
a <ul>
tag:
<ul class="reform-error-list">
<li>error 1</li>
<li>error 2</li>
<li>error n</li>
</ul>
You can use CSS to control the theming.
For even greater control we could use the Text.Reform.Generalized.errors
function:
> errors :: Monad m => > ([error] -> view) -- ^ function to convert the error messages into a view > -> Form m input error view () ()
This allows you to provide your own custom view code for rendering the errors.
We can wrap up the validForm
the exact same way we did postForm
:
> validPage :: AppT IO Response > validPage = > dir "valid" $ > appTemplate "valid post" () $ > <% reform (form "/valid") "valid" displayMessage Nothing validPostForm %> > where > displayMessage msg = appTemplate "Your Message" () $ renderMessage msg >
A few names have been changed, but everything else is exactly the same.
One of the primary motivations behind the changes in
digestive-functors 0.3
is allowing developers to separate the
validation code from the code which generates the view. We can do this
using reform
as well -- in a manner that is both more flexible and
which provides greater type safety. The key is the proof
parameter
-- which we have so far set to ()
and otherwise ignored.
In reform
we divide the work into two pieces:
Proofs
Form
that returns a Proved
valueThis allows the library authors to create Proofs
and demand that a Form
created by another developer satisfies the Proof
. At the same time, it gives the developer unrestricted control over the layout of the Form
-- including choice of templating library.
Let's create a new type alias for Form
that allows us to actually set the proof
parameter:
> type ProofForm proof = > Form IO [Input] AppError [AppT IO (XMLType (ServerPartT IO))] proof >
First we will explore the Proof
related code that would go into a library.
The proof
parameter for a Form
is used to indicate that something has been proven about the form's return value.
Two create a proof
we need two things:
We wrap those two pieces up into a Proof
:
> data Proof m error proof a b = Proof > { proofName :: proof -- ^ name of the thing to prove > , proofFunction :: a -> m (Either error b) -- ^ function which provides the proof > }
In validPostForm
, we checked that the input fields were not empty
Strings
. We could turn that check into a proof by first creating a
type to name that proof:
> data NotNull = NotNull >
and then creating a proof function like this:
> assertNotNull :: (Monad m) => error -> [a] -> m (Either error [a]) > assertNotNull errorMsg [] = return (Left errorMsg) > assertNotNull _ xs = return (Right xs) >
We can then wrap the two pieces up into a proof:
> notNullProof :: (Monad m) => > error -- ^ error to return if list is empty > -> Proof m error NotNull [a] [a] > notNullProof errorMsg = > Proof { proofName = NotNull > , proofFunction = assertNotNull errorMsg > } >
We can also create proofs that combine existing proofs. For example, a Message
is only valid if all its fields are not null. So, first thing we want to do is create a proof name for valid messages:
> data ValidMessage = ValidMessage >
The Message
constructor has the type:
> Message :: String -> String -> String -> Message
For SimpleForm
we would use pure
to turn Message
into a SimpleForm
:
> mkSimpleMessage :: SimpleForm (String -> String -> String -> Message) > mkSimpleMessage = pure Message
For ProofForm
, we can do the same thing use ipure
:
> mkMessage :: ProofForm (NotNull -> NotNull -> NotNull -> ValidMessage) > (String -> String -> String -> Message) > mkMessage = ipure (\NotNull NotNull NotNull -> ValidMessage) Message >
This creates a chain of validation since mkMessage
can only be applied to String
values that have been proven NotNull
.
The library author can now specify that the user supplied Form
has the type:
> someFunc :: ProofForm ValidMessage Message -> ...
You will notice that what we have constructed so far has imposes no restrictions on what types of form elements can be used, what template library must be used, or what web server must be used. At the same time, in order for the library user to create a ProofForm
with the required type, they must apply the supplied validators. Now, clearly a devious library user could use evil tricks to circumvent the system -- and they will get what they deserve.
To construct the Form
, we use a pattern very similar to what we did when using SimpleForm
. They only real differences are:
prove
instead of transformEither
<<*>>
instead of <*>
To apply a Proof
we use the prove
function:
> prove :: (Monad m) => > Form m input error view q a > -> Proof m error proof a b > -> Form m input error view proof b
So, we can make a ProofForms
for non-empty Strings
like this:
> inputText' :: String -> ProofForm NotNull String > inputText' initialValue = inputText initialValue `prove` (notNullProof Required) >
> textarea' :: Int -> Int -> String -> ProofForm NotNull String > textarea' cols rows initialValue = > textarea cols rows initialValue `prove` (notNullProof Required) >
to create the ValidMessage
form we can then combine the pieces like:
> provenPostForm :: ProofForm ValidMessage Message > provenPostForm = > mkMessage <<*>> errorList ++> label "name: " ++> inputText' "" > <<*>> errorList ++> label "title: " ++> inputText' "" > <<*>> errorList ++> label "message: " ++> textarea' 80 40 "" >
This code looks quite similar to our validPostForm
code. The primary
difference is that we use <<*>>
instead of <*>
. That brings is to the topic of type-indexed applicative functors.
Lets look at the type for Form
again:
> newtype Form m input error view proof a = Form { ... }
In order to make an Applicative
instance of Form
, all the proof type variables must be the same type and must form a Monoid
:
> instance (Functor m, Monad m, Monoid view, Monoid proof) => > (Form m input error view proof)
for SimpleForm
we used the following instance, which is defined for us already in reform
:
> instance (Functor m, Monoid view, Monad m) => Applicative (Form m input error view ())
With this instance, reform
feels and works almost exactly like digestive-functors <= 0.2
.
But, for the provePostForm
, that Applicative
instance won't work for us. mkMessage
has the type:
> mkMessage :: ProofForm (NotNull -> NotNull -> NotNull -> ValidMessage) > (String -> String -> String -> Message)
and we want to apply it to ProofForms
created by:
> inputText' :: String -> ProofForm NotNull String
Here the proof types don't match up. Instead we need a Applicative
Functor
that allows us to transform the return value and the proof
value. We need, what I believe is called, a Type-Indexed Applicative
Functor
or a Parameterized Applicative Functor
. Most literature on
this subject is actually dealing with type-indexed or parameterized
Monads
, but the idea is the same.
The reform
library defines two new classes, IndexedFunctor
and IndexedApplicative
:
> class IndexedFunctor f where > -- | imap is similar to fmap > imap :: (x -> y) -- ^ function to apply to first parameter > -> (a -> b) -- ^ function to apply to second parameter > -> f x a -- ^ indexed functor > -> f y b
> class (IndexedFunctor f) => IndexedApplicative f where > -- | similar to 'pure' > ipure :: x -> a -> f x a > -- | similar to '<*>' > (<<*>>) :: f (x -> y) (a -> b) -> f x a -> f y b
These classes look just like their non-indexed counterparts, except that they transform an extra parameter. Now we can create instances like:
> instance (Monad m) => IndexedFunctor (Form m input view error) where > instance (Monad m, Monoid view) => IndexedApplicative (Form m input error view) where
We use these classes the same way we would use the normal Functor
and Applicative
classes. The only difference is that the type-checker can now enforce the proofs.
Proofs
in unproven Forms
The Proof
module provides a handful of useful Proofs
that perform
transformations, such as converting a String
to a Int
:
> decimal :: (Monad m, Eq i, Num i) => > (String -> error) -- ^ create an error message ('String' is the value > -- that did not parse) > -> Proof m error Decimal String i
We can use this Proof
with our SimpleForm
by using the transform
function:
> transform :: (Monad m) => > Form m input error view anyProof a > -> Proof m error proof a b > -> Form m input error view () b
transform
is similar to the prove
function, except it ignores the proof name and sets the proof to ()
. Technically ()
is still a proof -- but we consider it to be the proof that proves nothing.
Here is an example of using transform
with decimal
to create a
simple form that parses a positive Integer
value:
> inputInteger :: SimpleForm Integer > inputInteger = inputText "" `transform` (decimal NotANatural) >
And, that is the essence of reform
. The Haddock documentation should cover the remainder -- such as other types of input controls (radio buttons, checkboxes, etc).
Here is a main function that ties all the examples together:
> main :: IO () > main = > simpleHTTP nullConf $ unXMLGenT $ > do decodeBody (defaultBodyPolicy "/tmp/" 0 10000 10000) > msum [ postPage > , postPage2 > , validPage > , do nullDir > appTemplate "forms" () $ > <ul> > <li><a href="/post">Simple Form</a></li> > <li><a href="/post2">Simple Form (postPage2 implementation)</a></li> > <li><a href="/valid">Valid Form</a></li> > </ul> > ] >
There is nothing reform
specific about.
[Source code for the app is here.]