DEV Community

Tito
Tito

Posted on • Updated on

A simple data oriented validation function

First Attempt

I'm going through Clojure for the Brave and True and one of the exercises was to write a function that validates a map against requirements.

I find this function quite beautiful because it declares the validations rules in a data oriented way.

; Define the requirements of each key in the record to be validated
; Name must be a key and glitter-index must be integer
(def requirements {:name string?
                   :glitter-index integer?})

; Validate a record (a map) against requirements
; Returns a list of validations results '(true false)
(defn validate
  [requirements record]
  ; map turns record {:a 1 :b 2} into a list of key-value pairs
  (map #((get requirements (key %)) (val %)) record))

; Usage:
(validate requirements {:name "Mr. Vampire" :glitter-index "42"})
; => (true false)
(validate requirements {:name "Mr. Vampire" :glitter-index 42})
; => (true true)
Enter fullscreen mode Exit fullscreen mode

The function is essentially a one liner and may be hard to grasp on first pass but once understood it starts to show some of the beauty of Clojure.

Second Attempt

I revisited this exercise after a couple of days and realized there was a simple but important bug.

Let say you wanted to update the requirements to check for age. You would update the requirement map but the validation function will ignore that new requirement.

(def requirements {:name string?
                   :glitter-index integer?
                   ; New requirement
                   :age integer?})

(validate requirements {:name "Edward Cullen", :glitter-index 10})
; Validate ignore the age requirement
; => (true, true)
Enter fullscreen mode Exit fullscreen mode

The cause is that the validate function uses the record map to determine which keys to check. The fix is simple and again begins to show the power of Clojure and s-expressions.

(defn old-validate
  [requirements record]
  ; Note how record was the input to map
  (map #((get requirements (key %)) (val %)) record))

(defn validate
  [requirements record]
  ; Note how requirements is now the input to map
  (map #((val %) (get record (key %))) requirements))

(validate requirements {:name "Edward Cullen", :glitter-index 10})
; :age is missing from the record and validate now returns a false
; => (true, true, false)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)