Validating data happens so often that it's good to have an EDSL to express the validation rules that our data has to pass. This makes the rules easier to create, understand, and maintain.
Valip (https://github.com/weavejester/valip) provides this. It's aimed at validating input from web forms, so it expects to validate maps with string values. We'll need to work around this expectation a time or two, but it isn't difficult.
We need to make sure that the Valip library is in our Leiningen project.clj
file:
(defproject cleaning-data "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/data.xml "0.0.8"] [valip "0.2.0"]])
Also, we need to load it into our script or REPL:
(use 'valip.core 'valip.predicates)
To validate some data, we have to define predicates to test the data fields against, then define the fields and predicates to validate, plus validate error messages.
(def user {:given-name "Fox" :surname "Mulder" :age 51 :badge "JTT047101111"})
present?
predicate defined by Valip fails if its input isn't a string:(defn number-present? [x] (and (present? (str x)) (or (instance? Integer x) (instance? Long x))))
(defn valid-badge [n] (not (nil? (re-find #"[A-Z]{3}d+" n))))
(defn validate-user [user] (validate user [:given-name present? "Given name required."] [:surname present? "Surname required."] [:age number-present? "Age required."] [:age (over 0) "Age should be positive."] [:age (under 150) "Age should be under 150."] [:badge present? "The badge number is required."] [:badge valid-badge "The badge number is invalid."]))
Now, we can easily validate data against this set of rules:
user=> (validate-user (assoc user :age -42)) {:age ["Age should be positive."]} user=> (validate-user (assoc user :age -42 :surname nil)) {:age ["Age should be positive."], :surname ["Surname required."]}