Skip to content

Instantly share code, notes, and snippets.

@remvee
Created November 2, 2015 09:44
Show Gist options
  • Save remvee/936b21e03add793e55e6 to your computer and use it in GitHub Desktop.
Save remvee/936b21e03add793e55e6 to your computer and use it in GitHub Desktop.
(ns #^{:author "Remco van 't Veer"
:doc
"Basic validation framework for maps. The constructed validator
functions take two arguments the before- and after state of the map.
They return a map of errors where keys are, typically, keys of the
input map and values are lists of keyworded errors or maps of keyword
errors to some relevant argument. For example:
((validator (not-blank :nr)
(numeric :nr)
(less-than 10 :count))
{} {:count 20})
Returns:
{:count [{:less-than 10}], :nr [:not-blank :numeric]}"}
remvee.validations
(:use [clojure.test]))
(defn not-blank
"Attribute may not be nil or empty string validation."
[k]
(fn [_ v]
(when (or (nil? (k v))
(= "" (k v)))
{k [:not-blank]})))
(defn numeric
"Attribute must be numeric validation."
[k]
(fn [_ v]
(when (not (number? (k v)))
{k [:numeric]})))
(defn less-than
"Attribute must be less then validation."
[amount k]
(fn [_ v]
(when (and (number? (k v))
(< amount (k v)))
{k [{:less-than amount}]})))
(defn unique
"Attribute must be uniq to collection returned by collfn."
{:test #(are [before after coll expected-errors]
(= expected-errors ((unique (fn [] coll) :id) before after))
nil ; before
{} ; after
[] ; coll
nil ; errors
nil ; before
{:id 1} ; after
[] ; coll
nil ; errors
nil ; before
{:id 2} ; after
[{:id 1}] ; coll
nil ; errors
{:id 1} ; before
{:id 1 :foo 1} ; after
[{:id 1}] ; coll
nil ; errors
nil ; before
{:id 1} ; after
[{:id 1}] ; coll
{:id [:unique]} ; errors
{:id 1 :bar 1} ; before
{:id 1 :foo 1} ; after
[{:id 1}] ; coll
{:id [:unique]} ; errors
)}
[collfn k]
(fn [a b]
(when (some #(= (k b) (k %))
(filter (partial not= a)
(collfn)))
{k [:unique]})))
(defn validator
"Return function to collect errors with validators. The errors from
the validators will be merged with concat."
{:test #(are [input errs]
(= errs ((validator (not-blank :name)
(not-blank :nr)
(numeric :nr)
(less-than 10 :count))
{} input))
{:nr 1, :name "test", :count 5}
nil
{:foo "bar"}
{:name [:not-blank], :nr [:not-blank :numeric]}
{:nr "foo"}
{:name [:not-blank], :nr [:numeric]}
{:nr 1, :name "test", :count 11}
{:count [{:less-than 10}]})}
[& validators]
(reduce (fn [c f]
(fn [a b]
(merge-with concat
(c a b)
(f a b))))
(fn [_ _] nil)
validators))
(defn meta-errors-validator
"Return a validator function which decorates the result map with a
meta map with error entry."
[& validators]
(let [f (apply validator validators)]
(fn [a b]
(if-let [e (f a b)]
(with-meta b (merge (meta b) {:errors e}))
b))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment