Skip to content

Instantly share code, notes, and snippets.

@ah45
Created September 4, 2023 12:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ah45/a898ef8e956672be9422a724590c0006 to your computer and use it in GitHub Desktop.
Save ah45/a898ef8e956672be9422a724590c0006 to your computer and use it in GitHub Desktop.
Using `juxt` to compose and apply multiple validation functions against the same value
(ns juxt-validate
(:require [clojure.string :as string]))
(defn validate-format
[v]
(if (every? #(contains? v %) #{:name :email :id})
[:ok v]
[:error {:invalid-format "expected id, name, and email"}]))
(defn validate-email
[v]
(if (some-> v :email (string/includes? "@"))
[:ok v]
[:error {:invalid-email "email is invalid"}]))
(defn validate-name
[v]
(if-not (some->> v :name str (re-find #"\d+"))
[:ok v]
[:error {:invalid-name "names cannot contain numbers"}]))
(defn collect-validation-results
[{error-merge-fn :error-merge-fn
:or {error-merge-fn identity}}
validators
value]
(let [r ((apply juxt validators) value)
{ok :ok
err :error} (group-by first r)]
(if (seq err)
[:error (error-merge-fn (map second err))]
[:ok (last (last ok))])))
(defn merge-tagged-map-errors
[errs]
(let [merge-maps (fn [x y] (merge-with #(conj (if (coll? %1) %1 [%1]) %2) x y))]
(reduce
merge-maps
errs)))
(defn validate-request
[r]
(collect-validation-results
{:error-merge-fn merge-tagged-map-errors}
[validate-format
validate-email
validate-name]
r))
(comment
(validate-request {}) ;=> [:error {:invalid-format "…", :invald-email "…"}]
(validate-request {:email "a" :name 1}) ;=> [:error {:invalid-format "…", :invald-email "…", :invalid-name "…"}]
(validate-request {:id 1 :email "a@b.com" :name "A B"}) ;=> [:ok {:id 1 :email "a@b.com" :name "A B"}]
:rcf)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment