Skip to content

Instantly share code, notes, and snippets.

@mbezjak
Last active December 29, 2022 19:23
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 mbezjak/1112a321d12c7aaf41a2d7140f2a535a to your computer and use it in GitHub Desktop.
Save mbezjak/1112a321d12c7aaf41a2d7140f2a535a to your computer and use it in GitHub Desktop.
Additional functions for the error model
(ns my.company.errors
"Errors is a collection of `error`."
(:require
[my.company.coll :as coll]
[my.company.error :as error]))
;; Functions in addition to:
;; https://gist.github.com/mbezjak/c1baeece563b8ed734692938e6d1a36f
(defn validate
"Collect validation errors from `results`.
This is a way to collect many errors at once. E.g.
(errors/validate
(when something-1 {:code ...})
(when something-2 {:code ...})
(task/validate ...)
(for [x xs]
{:code ...}))
`results` can be (as seen above):
- a collection of nested results
- nil
- error
- errors
On successful validation returns `nil`."
[& results]
(->> results
(flatten)
(remove nil?)
(make)
(not-empty)))
(defn lazy-validate-fns
"Evaluate `fns` lazily, moving on in case of success."
[& fns]
(loop [[f & rst] fns
val nil]
(if (or (some? val) (not f))
val
(recur rst (validate (f))))))
(def continue-if-above-success
"This value is not used. It's just here to satisfy the linter and make the macro below a bit easier to use.")
(defmacro validate-groups
"Breaks code into multiple groups, lazily evaluates one and moves on in case of success."
[& form]
(let [split-fn #(= 'errors/continue-if-above-success %)
into-fn (fn [validation-group] `(fn [] ~(vec validation-group)))
fns (->> form (coll/splits-by split-fn) (map into-fn))]
`(lazy-validate-fns ~@fns)))
(defn throw! [es]
(when (seq es)
(throw (ex-info "Validation failure"
{::validation? true
::errors es}))))
(defn validation-exception? [exception]
(-> exception ex-data ::validation? true?))
(defn unwrap-exception [exception]
(-> exception ex-data ::errors))
(ns my.company.errors-test
(:require
[clojure.test :refer [deftest is]]
[my.company.errors :as sut]))
(deftest validate
(is (nil? (sut/validate)))
(is (nil? (sut/validate nil)))
(is (nil? (sut/validate (sut/validate nil))))
(is (= [:error/a] (sut/codes (sut/validate {:code :error/a}))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate
{:code :error/a}
{:code :error/b}))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate
nil
{:code :error/a}
nil
{:code :error/b}
nil))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate
nil
[{:code :error/a}]
nil
[[[{:code :error/b}]]]
nil))))
(is (= [:error/a]
(sut/codes (sut/validate (sut/validate {:code :error/a})))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate
(sut/validate {:code :error/a})
{:code :error/b})))))
(deftest lazy-validate-fns
(is (nil? (sut/lazy-validate-fns)))
(is (nil? (sut/lazy-validate-fns (constantly nil))))
(is (nil? (sut/lazy-validate-fns (constantly nil) (constantly nil))))
(is (nil? (sut/lazy-validate-fns (constantly []))))
(is (nil? (sut/lazy-validate-fns (constantly [nil nil]))))
(is (= [:error/a]
(sut/codes
(sut/lazy-validate-fns (constantly nil) (constantly {:code :error/a})))))
(is (= [:error/a]
(sut/codes
(sut/lazy-validate-fns (constantly {:code :error/a})
(constantly {:code :error/b})))))
(is (= [:error/a :error/b]
(sut/codes
(sut/lazy-validate-fns (constantly [{:code :error/a} {:code :error/b}]))))))
(deftest validate-groups
(is (nil? (sut/validate-groups)))
(is (nil? (sut/validate-groups nil)))
(is (nil? (sut/validate-groups [nil])))
(is (= [:error/a] (sut/codes (sut/validate-groups {:code :error/a}))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate-groups
{:code :error/a}
{:code :error/b}))))
(is (= [:error/a :error/b]
(sut/codes (sut/validate-groups
{:code :error/a}
{:code :error/b}
errors/continue-if-above-success
{:code :error/c}))))
(is (= [:error/c]
(sut/codes (sut/validate-groups
nil
errors/continue-if-above-success
{:code :error/c})))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment