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.