Validations tell us that some data is valid.
(valid? 1) => true
And some data is invalid.
(valid? 2) => false
But not all data is valid in all circumstances. So we need more than one function.
(even? 2) => true
(neg? 2) => false
We should be able to define our own validations.
(silly? :clown-shoes) => true
And have functions that construct validations.
(def my-validation
(after #inst "2013-12-01"))
(my-validation #inst "2013-12-02") => true
To check more than one rule at a time we need a way of composing validations.
(def during-summer?
(join (after #inst "2013-12-01")
(before #inst "2014-03-01")))
The composed validations should be composeable as well, so that they may be re-used.
(def good-day-for-a-picnic?
(join during-summer?
on-weekend?))
(good-day-for-a-picnic? #inst "2013-04-14")
=> false
Now we know our data is bad, but not why. We need more information.
(good-day-for-a-picnic? #inst "2013-04-14")
=> "That's not during summer"
Of course data can be wrong in more than one way.
(good-day-for-a-picnic? #inst "2013-04-15")
=> ["That's not during summer"
"Mondays suck for picnics"]
Though sometimes the reasons are redundant.
(def strong-password?
(join not-empty?
(matches? #"[a-z]")
(matches? #"[A-Z]")
(matches? #"[0-9]")))
(strong-password? "")
=> ["You didn't give a password"
"The password you didn't give has no lower case letters"
"Or any upper case letters for that matter"
"Unsurprisingly it was missing numbers too"]
We should be able to say when a branch of validation should fail fast.
(def strong-password?
(chain not-empty?
(join (matches? #"[a-z]")
(matches? #"[A-Z]")
(matches? #"[0-9]"))))
(strong-password? "")
=> ["You didn't give a password"]
But error messages are useless if you can't read them.
(def errors
(strong-password? "kiTTenS"))
(english errors)
=> ["Your password must contain numbers"]
(russian errors)
=> ["Вы должны быть пьян"]
Even computers should be able to understand them.
errors
=> [{:type :matches
:pattern #"[0-9]"}]
(track-common-errors! errors)
=> {:contains 22
:not-empty? 400}
And I think it goes without saying that your validations should be able to validate any type of data.
(prime? 3)
=> []
(not-empty? "")
=> [{:type :not-empty?}]
((length-eq? 3) [1 2 3])
=> []
((has-keys? :name :age) {:name "Logan"})
=> [{:type :has-keys? :key :age}]
And they shouldn't be coupled to a web framework, or a database layer or a CSV importer.
If you agree with me then try Vlad.