Search code examples
clojure

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 don't want to use exceptions because the violations are expected.
  • ->some would not work because it would get only the first nil occurrence. And I need to push all violations to a collection of violations.

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)))

Solution

  • 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"]}