Input Validation
Luminus uses Bouncer as the default validation library. Bouncer is a Clojure/Script library and allows us to share validation logic between the client and the server.
Bouncer provides bouncer.core/validate
and bouncer.core/valid?
functions for handling validation. These functions each accept a map containing the parameters followed by the validators.
Bouncer provides the following validators out of the box:
required
args:[v]
- validates that the value is presentnumber
args:[v]
- validates that the value is numericpositive
args:[v]
- validates that the value is a positive numbermember
args:[v coll]
- validates whether the value is a member of a collectioncustom
args:[v pred]
- validates the value using a custom validatorevery
args:[coll pred]
- checks that every member of the collection matches the predicatematches
args:[v regex]
- checks that the value matches the suppied regexemail
args:[v]
- checks that the value is an email addressdatetime
args:[v & [format]]
- checks that the value is a date(time) with optional format stringmax-count
args:[coll n]
- validates that the collection has at most n elementsmin-count
args:[coll n]
- validates that the collection has at least n elements
Before we see how validation works, let's include bouncer.core
along with the bouncer.validators
in our namespace.
(ns myapp.home
(:require
...
[bouncer.core :as b]
[bouncer.validators :as v]))
Next, we'll define our map of parameter:
(def user {:id nil :pass "secret"})
We can now validate the data using some of the built in validators as follows:
(b/valid? user
:id v/required
:pass v/required)
The valid?
function will return a boolean value indicating whether the data is valid or not. When we want to see the errors then we must use the validate
function instead.
(b/validate user
:id v/required
:pass v/required)
The function returns a vector where the first element is the map of errors. The second element will be the original map with a :bouncer.core/errors
key appended in case of errors as seen below.
[{:id ("id must be present")}
{:bouncer.core/errors {:id ("id must be present")}
:id nil
:pass "secret"}]
We can apply multiple validators to the value by putting them into a vector:
(b/validate user
:id v/required
:pass [v/required [v/min-count 8]])
Note how validators that take additional parameters such as min-count
are placed in a vector, the value and will be passed in implicitly as the first parameter.
We can also validate nested maps:
(def person
{:address
{:unit 10
:street nil
:country "Canada"}})
(b/validate person
[:address :street] v/required
[:address :unit] v/number
[:address :phone] [[v/matches #"^\d+$"]])
Finally, we can easily define custom validators using bouncer.validators/defvalidator
:
(v/defvalidator valid-password
{:default-message-format "%s must be at least 7 characters long"}
[p]
(and p (> (count p) 7)))
(b/validate user
:id v/required
:pass valid-password)
For further examples, please refer to the official project page.