brenobarreto
brenobarreto

Reputation: 325

Best way to do multiple checks in Clojure while accumulating all violations

I have several Clojure predicates which validate the application input against different business rules. I'm wondering what is the best strategy to walk through this pipe of predicates considering that:

I considered the following approach but I have doubts about it.

(defn validate [arg]
  (cond
    (logic/check-one? arg) (add-violation-one)
    (logic/check-two? arg) (add-violation-two)))

Upvotes: 0

Views: 209

Answers (1)

cfrick
cfrick

Reputation: 37073

One way to do this is with cond->. It runs all the checks and it pases the result of a branch down. E.g.

(let [arg 42
      add-error #(update %1 :errors conj %2)] 
  (cond-> {:errors []}
    (even? arg) (add-error "must not be even")
    (odd? arg)  (add-error "must not be odd")
    (zero? arg) (add-error "must not be zero")
    (pos? arg)  (add-error "must not be positive")))
; → {:errors ["must not be even" "must not be positive"]}

This approach makes most sense, if you really want to write out those things by hand. It's maybe more flexible to not directly rely on something from core and write your own function. E.g. you could reduce over tuples of predicate and error handler.

(defn validate 
  [checks errors arg]
  (reduce 
    (fn validation-step [errors [pred handle-error]]
      (if-let [result (pred arg)]
        (update errors :errors conj (handle-error result))
        errors))
    errors
    checks))

(prn
  (validate
    [[even? (constantly "must not be even")]
     [odd?  (constantly "must not be odd")]
     [zero? (constantly "must not be zero")]
     [pos?  (constantly "must not be positive")]]
    {:errors []}
    42))
; → {:errors ["must not be even" "must not be positive"]}

Upvotes: 3

Related Questions