Introduction
IHP provides a simple infrastructure for validating incoming data. This guide covers validating new and existing records, as well as complex validations with database access.Quickstart
Setting up the controller
Assume we have aPosts controller with title and body fields. The CreatePostAction looks like:
Adding Validation Logic
To make sure that thetitle and body are not empty, use validateField ... nonEmpty:
Common Validators
Here is a list of the most common validators: Works with Text fields:|> validateField #name nonEmpty|> validateField #email isEmail|> validateField #phoneNumber isPhoneNumber
|> validateField #rating (isInRange (1, 10))
Validate Maybe Fields
You can use all the existing validators with Maybe fields. The validator will only be applied when the field is not Nothing:
Fill Validation
When usingfill, like |> fill @'["title", "body"], any error parsing the input is also added as a validation error.
For example, when fill fills in an integer attribute, but the string "hello" is submitted, an error will be added to the record and the record is not valid anymore. The record attribute will keep its old value (before applying fill) and later re-render in the error case of ifValid.
Rendering Errors
When a post with an empty title is submitted to this action, an error message will be written into the record. The call to|> ifValid will see the error and run the Left post -> render NewView { post }, which displays the form to the user again.
The default form helpers like {textField #title} automatically render the error message below the field:
Validating An Email Is Unique
For example, when dealing with users, you usually want to make sure that an email is only used once for a single user. You can use|> validateIsUnique #email to validate that an email is unique for a given record.
This function queries the database and checks whether there exists a record with the same email value. The function ignores the current entity of course.
This function does IO, so any further arrows have to be >>=:
Case Insensitive Uniqueness
Usually emails like[email protected] and [email protected] belong to the same person. Use validateIsUniqueCaseInsensitive to ignore the case:
Schema.sql:
Sharing Between Create and Update Action
Usually, you have a lot of the same validation logic when creating and updating a record. To avoid duplicating the validation rules, apply them inside thebuildPost function:
buildPost function.
Creating a Custom Validator
You can write your constraint like this:Creating a Custom Validator that Uses IO
UsevalidateFieldIO to validate a field based upon a value that is fetched from the database. The below example shows how to validate that a post’s title field contains a unique value:
buildPost function, since it is now inside IO, use the >>= (bind) operator:
Checking If A Record Is Valid
UseifValid to check for validity of a record:
Customizing Error Messages
Use withCustomErrorMessage
Customize the error message when validation failed:
nonEmpty adds an error to the user, the message Please enter your firstname will be used instead of the default This field cannot be empty.
Use withCustomErrorMessageIO
Customize the error message when using IO functions:
Security Concerns and Conditional fill
It’s important to remember that any kind of validations you might have on the form level are not enough to ensure the security of your application. You should always have validations on the backend as well. The user might manipulate the form data and send invalid data to your application.
You don’t have to always fill all fields in one go. Sometimes you’d like to conditionally fill based on the current user or based on the current logic.
Let’s say we have a Comment record that has a postId that references a Post, a body field, and a moderation field allowing admin users to indicate if they are approved or rejected.
postId. Once a comment is referencing a post it will never have the reference change. So it means we should fill it only upon creation:
currentUserIsAdmin indicating if the current user is an admin. We’d like to allow only admins to set the moderation status of a comment:
Internals
validateField
The primary operation is validateField #field validationFunction record.
This function does the following:
- Read the
#fieldfrom the record - Apply the
validationFunctionto the field value - When the validator returns errors, store the errors inside the
metaattribute of the record
validateField function expects the record to have a field meta :: MetaBag. This meta field is used to store validation errors.
MetaBag. When you apply another validateField to the record, the errors will be appended to the annotations list.
Validation Functions
A validation function is just a function which, given a value, returnsSuccess or Failure "some error message".
Here is an example:
isColor "#ffffff" will return Success. Calling isColor "something bad" will result in Failure "is not a valid color".
Attaching Errors To A Record Field
You can attach errors to a specific field of a record even when not validating:Attaching Errors with HTML
If you try to use HTML code withinattachFailure, the HTML code will be escaped. Use attachFailureHtml instead:
Retrieving The First Error Message For A Field
You can access an error for a specific field usinggetValidationFailure:
Just "Field cannot be empty" or Nothing when the post has a title.
Retrieving All Error Messages For A Record
Access them from themeta :: MetaBag attribute:
[(Text, Violation)], e.g. [("name", "This field cannot be empty")].