0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-11 23:31:21 -05:00

♻️ Improve the asserts framework

This commit is contained in:
Andrey Antukh 2022-07-07 12:27:31 +02:00
parent c02e8ff883
commit 98190ed92d
2 changed files with 88 additions and 61 deletions

View file

@ -5,7 +5,7 @@
;; Copyright (c) UXBOX Labs SL
(ns app.common.spec
"Data manipulation and query helper functions."
"Data validation & assertion helpers."
(:refer-clojure :exclude [assert bytes?])
#?(:cljs (:require-macros [app.common.spec :refer [assert]]))
(:require
@ -31,8 +31,6 @@
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
(def valid? s/valid?)
;; --- Conformers
(defn uuid-conformer
@ -220,73 +218,102 @@
(fn [s]
(str/join "," s))))
;; --- Macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MACROS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn spec-assert*
[spec val hint ctx]
(if (s/valid? spec val)
val
(let [data (s/explain-data spec val)]
(ex/raise :type :assertion
:code :spec-validation
:hint hint
::ex/data (merge ctx data)))))
(defn explain-data
[spec value]
(s/explain-data spec value))
(defmacro assert
"Development only assertion macro."
[spec x]
(when *assert*
(let [nsdata (:ns &env)
context (if nsdata
{:ns (str (:name nsdata))
:name (pr-str spec)
:line (:line &env)
:file (:file (:meta nsdata))}
(let [mdata (meta &form)]
{:ns (str (ns-name *ns*))
:name (pr-str spec)
:line (:line mdata)}))
message (str "spec assert: '" (pr-str spec) "'")]
`(spec-assert* ~spec ~x ~message ~context))))
(defn valid?
[spec value]
(s/valid? spec value))
(defmacro verify
"Always active assertion macro (does not obey to :elide-asserts)"
[spec x]
(let [nsdata (:ns &env)
context (when nsdata
(defmacro assert-expr*
"Auxiliar macro for expression assertion."
[expr hint]
`(when-not ~expr
(ex/raise :type :assertion
:code :expr-validation
:hint ~hint)))
(defmacro assert-spec*
"Auxiliar macro for spec assertion."
[spec value hint]
(let [context (if-let [nsdata (:ns &env)]
{:ns (str (:name nsdata))
:name (pr-str spec)
:line (:line &env)
:file (:file (:meta nsdata))})
message (str "spec verify: '" (pr-str spec) "'")]
`(spec-assert* ~spec ~x ~message ~context)))
:file (:file (:meta nsdata))}
{:ns (str (ns-name *ns*))
:name (pr-str spec)
:line (:line (meta &form))})
hint (or hint (str "spec assert: " (pr-str spec)))]
`(if (valid? ~spec ~value)
~value
(let [data# (explain-data ~spec ~value)]
(ex/raise :type :assertion
:code :spec-validation
:hint ~hint
::ex/data (merge ~context data#))))))
(defmacro assert
"Is a spec specific assertion macro that only evaluates if *assert*
is true. DEPRECATED: it should be replaced by the new, general
purpose assert! macro."
[spec value]
(when *assert*
`(assert-spec* ~spec ~value nil)))
(defmacro verify
"Is a spec specific assertion macro that evaluates always,
independently of *assert* value. DEPRECATED: should be replaced by
the new, general purpose `verify!` macro."
[spec value]
`(assert-spec* ~spec ~value nil))
(defmacro assert!
"General purpose assertion macro."
[& {:keys [expr spec always? hint val]}]
(cond
(some? spec)
(let [context (if-let [nsdata (:ns &env)]
{:ns (str (:name nsdata))
:name (pr-str spec)
:line (:line &env)
:file (:file (:meta nsdata))}
{:ns (str (ns-name *ns*))
:name (pr-str spec)
:line (:line (meta &form))})
message (or hint (str "spec assert: " (pr-str spec)))]
(when (or always? *assert*)
`(spec-assert* ~spec ~val ~message ~context)))
[& params]
;; If we only receive two arguments, this means we use the simplified form
(let [pcnt (count params)]
(cond
;; When we have a single argument, this means a simplified form
;; of expr assertion
(= 1 pcnt)
(let [expr (first params)
hint (str "expr assert failed:" (pr-str expr))]
(when *assert*
`(assert-expr* ~expr ~hint)))
(some? expr)
(let [message (or hint (str "expr assert: " (pr-str expr)))]
(when (or always? *assert*)
`(when-not ~expr
(ex/raise :type :assertion
:code :expr-validation
:hint ~message))))
;; If we have two arguments, this can be spec or expr
;; assertion. The spec assertion is determined if the first
;; argument is a qualified keyword.
(= 2 pcnt)
(let [[spec-or-expr value-or-msg] params]
(if (qualified-keyword? spec-or-expr)
`(assert-spec* ~spec-or-expr ~value-or-msg nil)
`(assert-expr* ~spec-or-expr ~value-or-msg)))
:else nil))
(= 3 pcnt)
(let [[spec value hint] params]
`(assert-spec* ~spec ~value ~hint))
:else
(let [{:keys [spec expr hint always? val]} params]
(when (or always? *assert*)
(if spec
`(assert-spec* ~spec ~val ~hint)
`(assert-expr* ~expr ~hint)))))))
(defmacro verify!
"A variant of `assert!` macro that evaluates always, independently
of the *assert* value."
[& params]
(binding [*assert* true]
`(assert! ~@params)))
;; --- Public Api

View file

@ -111,11 +111,11 @@
;; --- Helper Functions
(defn ^boolean check-browser? [candidate]
(us/verify ::browser candidate)
(us/verify! ::browser candidate)
(= candidate @browser))
(defn ^boolean check-platform? [candidate]
(us/verify ::platform candidate)
(us/verify! ::platform candidate)
(= candidate @platform))
(defn resolve-profile-photo-url