mirror of
https://github.com/penpot/penpot.git
synced 2025-02-12 18:18:24 -05:00
♻️ Clean assertion and schema chechking API
This commit is contained in:
parent
45f3a67950
commit
002b1679c3
13 changed files with 161 additions and 168 deletions
|
@ -278,25 +278,18 @@
|
|||
:inc 1)
|
||||
message)
|
||||
|
||||
(def ^:private schema:params
|
||||
[:map {:title "params"}
|
||||
[:session-id ::sm/uuid]])
|
||||
|
||||
(def ^:private decode-params
|
||||
(sm/decoder schema:params sm/json-transformer))
|
||||
|
||||
(def ^:private validate-params!
|
||||
(sm/validate-fn schema:params))
|
||||
|
||||
(defn- http-handler
|
||||
[cfg {:keys [params ::session/profile-id] :as request}]
|
||||
(let [{:keys [session-id]} (-> params
|
||||
decode-params
|
||||
validate-params!)]
|
||||
(let [session-id (some-> params :session-id sm/parse-uuid)]
|
||||
(when-not (uuid? session-id)
|
||||
(ex/raise :type :validation
|
||||
:code :missing-session-id
|
||||
:hint "missing or invalid session-id found"))
|
||||
|
||||
(cond
|
||||
(not profile-id)
|
||||
(ex/raise :type :authentication
|
||||
:hint "Authentication required.")
|
||||
:hint "authentication required")
|
||||
|
||||
;; WORKAROUND: we use the adapter specific predicate for
|
||||
;; performance reasons; for now, the ring default impl for
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.http.client :as http]
|
||||
|
@ -19,26 +20,26 @@
|
|||
[datoteka.fs :as fs]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(def ^:private
|
||||
schema:template
|
||||
(def ^:private schema:template
|
||||
[:map {:title "Template"}
|
||||
[:id ::sm/word-string]
|
||||
[:name ::sm/word-string]
|
||||
[:file-uri ::sm/word-string]])
|
||||
|
||||
(def ^:private
|
||||
schema:templates
|
||||
(def ^:private schema:templates
|
||||
[:vector schema:template])
|
||||
|
||||
(def check-templates!
|
||||
(sm/check-fn schema:templates
|
||||
:code :invalid-templates
|
||||
:hint "invalid templates"))
|
||||
|
||||
(defmethod ig/init-key ::setup/templates
|
||||
[_ _]
|
||||
(let [templates (-> "app/onboarding.edn" io/resource slurp edn/read-string)
|
||||
templates (check-templates! templates)
|
||||
dest (fs/join fs/*cwd* "builtin-templates")]
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid templates file"
|
||||
(sm/check! schema:templates templates))
|
||||
|
||||
(doseq [{:keys [id path] :as template} templates]
|
||||
(let [path (or path (fs/join dest id))]
|
||||
(if (fs/exists? path)
|
||||
|
@ -58,9 +59,9 @@
|
|||
(let [resp (http/req! cfg
|
||||
{:method :get :uri (:file-uri template)}
|
||||
{:response-type :input-stream :sync? true})]
|
||||
|
||||
(dm/verify!
|
||||
"unexpected response found on fetching template"
|
||||
(= 200 (:status resp)))
|
||||
(when-not (= 200 (:status resp))
|
||||
(ex/raise :type :internal
|
||||
:code :unexpected-status-code
|
||||
:hint (str "unable to download template, recevied status " (:status resp))))
|
||||
|
||||
(io/input-stream (:body resp)))))))
|
||||
|
|
|
@ -155,9 +155,10 @@
|
|||
|
||||
(defn enable-team-feature!
|
||||
[team-id feature]
|
||||
(dm/verify!
|
||||
"feature should be supported"
|
||||
(contains? cfeat/supported-features feature))
|
||||
(when-not (contains? cfeat/supported-features feature)
|
||||
(ex/raise :type :assertion
|
||||
:code :feature-not-supported
|
||||
:hint (str "feature '" feature "' not supported")))
|
||||
|
||||
(let [team-id (h/parse-uuid team-id)]
|
||||
(db/tx-run! main/system
|
||||
|
@ -173,9 +174,11 @@
|
|||
|
||||
(defn disable-team-feature!
|
||||
[team-id feature]
|
||||
(dm/verify!
|
||||
"feature should be supported"
|
||||
(contains? cfeat/supported-features feature))
|
||||
|
||||
(when-not (contains? cfeat/supported-features feature)
|
||||
(ex/raise :type :assertion
|
||||
:code :feature-not-supported
|
||||
:hint (str "feature '" feature "' not supported")))
|
||||
|
||||
(let [team-id (h/parse-uuid team-id)]
|
||||
(db/tx-run! main/system
|
||||
|
@ -203,9 +206,11 @@
|
|||
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
|
||||
:or {code :generic level :info}
|
||||
:as params}]
|
||||
(dm/verify!
|
||||
["invalid level %" level]
|
||||
(contains? #{:success :error :info :warning} level))
|
||||
|
||||
(when-not (contains? #{:success :error :info :warning} level)
|
||||
(ex/raise :type :assertion
|
||||
:code :incorrect-level
|
||||
:hint (str "level '" level "' not supported")))
|
||||
|
||||
(letfn [(send [dest]
|
||||
(l/inf :hint "sending notification" :dest (str dest))
|
||||
|
|
|
@ -108,14 +108,6 @@
|
|||
`(do ~@body)
|
||||
(reverse (partition 2 bindings))))
|
||||
|
||||
(defmacro check
|
||||
"Applies a predicate to the value, if result is true, return the
|
||||
value if not, returns nil."
|
||||
[pred-fn value]
|
||||
`(if (~pred-fn ~value)
|
||||
~value
|
||||
nil))
|
||||
|
||||
(defmacro get-prop
|
||||
"A macro based, optimized variant of `get` that access the property
|
||||
directly on CLJS, on CLJ works as get."
|
||||
|
@ -124,47 +116,32 @@
|
|||
(list 'js* (c/str "(~{}?." (str/snake prop) "?? ~{})") obj (list 'cljs.core/get obj prop))
|
||||
(list `c/get obj prop)))
|
||||
|
||||
(def ^:dynamic *assert-context* nil)
|
||||
(defn runtime-assert
|
||||
[hint f]
|
||||
(try
|
||||
(when-not (f)
|
||||
(throw (ex-info hint {:type :assertion
|
||||
:code :expr-validation
|
||||
:hint hint})))
|
||||
(catch #?(:clj Throwable :cljs :default) cause
|
||||
(let [data (-> (ex-data cause)
|
||||
(assoc :type :assertion)
|
||||
(assoc :code :expr-validation)
|
||||
(assoc :hint hint))]
|
||||
(throw (ex-info hint data cause))))))
|
||||
|
||||
(defmacro assert!
|
||||
([expr]
|
||||
`(assert! nil ~expr))
|
||||
([hint expr]
|
||||
(let [hint (cond
|
||||
(vector? hint)
|
||||
`(str/ffmt ~@hint)
|
||||
(let [hint (cond
|
||||
(vector? hint)
|
||||
`(str/ffmt ~@hint)
|
||||
|
||||
(some? hint)
|
||||
hint
|
||||
(some? hint)
|
||||
hint
|
||||
|
||||
:else
|
||||
(str "expr assert: " (pr-str expr)))]
|
||||
:else
|
||||
(str "expr assert: " (pr-str expr)))]
|
||||
(when *assert*
|
||||
`(binding [*assert-context* ~hint]
|
||||
(when-not ~expr
|
||||
(let [hint# ~hint
|
||||
params# {:type :assertion
|
||||
:code :expr-validation
|
||||
:hint hint#}]
|
||||
(throw (ex-info hint# params#)))))))))
|
||||
|
||||
(defmacro verify!
|
||||
([expr]
|
||||
`(verify! nil ~expr))
|
||||
([hint expr]
|
||||
(let [hint (cond
|
||||
(vector? hint)
|
||||
`(str/ffmt ~@hint)
|
||||
|
||||
(some? hint)
|
||||
hint
|
||||
|
||||
:else
|
||||
(str "expr assert: " (pr-str expr)))]
|
||||
`(binding [*assert-context* ~hint]
|
||||
(when-not ~expr
|
||||
(let [hint# ~hint
|
||||
params# {:type :assertion
|
||||
:code :expr-validation
|
||||
:hint hint#}]
|
||||
(throw (ex-info hint# params#))))))))
|
||||
`(runtime-assert ~hint (fn [] ~expr))))))
|
||||
|
|
|
@ -414,10 +414,11 @@
|
|||
;; If object has changed or is new verify is correct
|
||||
(when (and (some? shape-new)
|
||||
(not= shape-old shape-new))
|
||||
(dm/verify!
|
||||
"expected valid shape"
|
||||
(and (cts/valid-shape? shape-new)
|
||||
(cts/shape? shape-new))))))]
|
||||
(when-not (and (cts/valid-shape? shape-new)
|
||||
(cts/shape? shape-new))
|
||||
(ex/raise :type :assertion
|
||||
:code :data-validation
|
||||
:hint "invalid shape found after applying changes")))))]
|
||||
|
||||
(->> (into #{} (map :page-id) items)
|
||||
(mapcat (fn [page-id]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#?(:cljs (:require-macros [app.common.schema :refer [ignoring]]))
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.schema.openapi :as-alias oapi]
|
||||
|
@ -243,59 +242,35 @@
|
|||
(defn- fast-check!
|
||||
"A fast path for checking process, assumes the ILazySchema protocol
|
||||
implemented on the provided `s` schema. Sould not be used directly."
|
||||
[s value]
|
||||
[s type code hint value]
|
||||
(when-not ^boolean (-validate s value)
|
||||
(let [hint (d/nilv dm/*assert-context* "check error")
|
||||
explain (-explain s value)]
|
||||
(throw (ex-info hint {:type :assertion
|
||||
:code :data-validation
|
||||
(let [explain (-explain s value)]
|
||||
(throw (ex-info hint {:type type
|
||||
:code code
|
||||
:hint hint
|
||||
::explain explain}))))
|
||||
true)
|
||||
value)
|
||||
|
||||
(declare ^:private lazy-schema)
|
||||
|
||||
(defn check-fn
|
||||
"Create a predefined check function"
|
||||
[s]
|
||||
(let [schema (if (lazy-schema? s) s (lazy-schema s))]
|
||||
(partial fast-check! schema)))
|
||||
[s & {:keys [hint type code]}]
|
||||
(let [schema (if (lazy-schema? s) s (lazy-schema s))
|
||||
hint (or ^boolean hint "check error")
|
||||
type (or ^boolean type :assertion)
|
||||
code (or ^boolean code :data-validation)]
|
||||
(partial fast-check! schema type code hint)))
|
||||
|
||||
(defn check!
|
||||
"A helper intended to be used on assertions for validate/check the
|
||||
schema over provided data. Raises an assertion exception, should be
|
||||
used together with `dm/assert!` or `dm/verify!`."
|
||||
[s value]
|
||||
(let [s (if (lazy-schema? s) s (lazy-schema s))]
|
||||
(fast-check! s value)))
|
||||
|
||||
(defn- fast-validate!
|
||||
"A fast path for validation process, assumes the ILazySchema protocol
|
||||
implemented on the provided `s` schema. Sould not be used directly."
|
||||
([s value] (fast-validate! s value nil))
|
||||
([s value options]
|
||||
(when-not ^boolean (-validate s value)
|
||||
(let [explain (-explain s value)
|
||||
options (into {:type :validation
|
||||
:code :data-validation
|
||||
::explain explain}
|
||||
options)
|
||||
hint (get options :hint "schema validation error")]
|
||||
(throw (ex-info hint options))))
|
||||
value))
|
||||
|
||||
(defn validate-fn
|
||||
"Create a predefined validate function that raises an expception"
|
||||
[s]
|
||||
(let [schema (if (lazy-schema? s) s (lazy-schema s))]
|
||||
(partial fast-validate! schema)))
|
||||
|
||||
(defn validate!
|
||||
"A generic validation function for predefined schemas."
|
||||
([s value] (validate! s value nil))
|
||||
([s value options]
|
||||
(let [s (if (lazy-schema? s) s (lazy-schema s))]
|
||||
(fast-validate! s value options))))
|
||||
schema over provided data. Raises an assertion exception."
|
||||
[s value & {:keys [hint type code]}]
|
||||
(let [s (if (lazy-schema? s) s (lazy-schema s))
|
||||
hint (or ^boolean hint "check error")
|
||||
type (or ^boolean type :assertion)
|
||||
code (or ^boolean code :data-validation)]
|
||||
(fast-check! s type code hint value)))
|
||||
|
||||
(defn register! [type s]
|
||||
(let [s (if (map? s) (m/-simple-schema s) s)]
|
||||
|
@ -1005,6 +980,12 @@
|
|||
(def check-email!
|
||||
(check-fn ::email))
|
||||
|
||||
(def check-uuid!
|
||||
(check-fn ::uuid :hint "expected valid uuid instance"))
|
||||
|
||||
(def check-string!
|
||||
(check-fn :string :hint "expected string"))
|
||||
|
||||
(def check-coll-of-uuid!
|
||||
(check-fn ::coll-of-uuid))
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
|
@ -29,12 +30,12 @@
|
|||
{:x 0 :y 0 :width 1 :height 1})
|
||||
|
||||
(defn- assert-valid-num [attr num]
|
||||
(dm/verify!
|
||||
["%1 attribute has invalid value: %2" (d/name attr) num]
|
||||
(and (d/num? num)
|
||||
(<= num max-safe-int)
|
||||
(>= num min-safe-int)))
|
||||
|
||||
(when-not (and (d/num? num)
|
||||
(<= num max-safe-int)
|
||||
(>= num min-safe-int))
|
||||
(ex/raise :type :assertion
|
||||
:code :data-validation
|
||||
:hint (str "invalid numeric value for `" attr "`: " num)))
|
||||
(cond
|
||||
(and (> num 0) (< num 1)) 1
|
||||
(and (< num 0) (> num -1)) -1
|
||||
|
@ -43,19 +44,21 @@
|
|||
(defn- assert-valid-pos-num
|
||||
[attr num]
|
||||
|
||||
(dm/verify!
|
||||
["%1 attribute should be positive" (d/name attr)]
|
||||
(pos? num))
|
||||
|
||||
(when-not (pos? num)
|
||||
(ex/raise :type :assertion
|
||||
:code :data-validation
|
||||
:hint (str "invalid numeric value for `" attr "`: " num " (should be positive)")))
|
||||
num)
|
||||
|
||||
(defn- assert-valid-blend-mode
|
||||
[mode]
|
||||
(let [clean-value (-> mode str/trim str/lower keyword)]
|
||||
(dm/verify!
|
||||
["%1 is not a valid blend mode" clean-value]
|
||||
(contains? cts/blend-modes clean-value))
|
||||
clean-value))
|
||||
(let [value (-> mode str/trim str/lower keyword)]
|
||||
|
||||
(when-not (contains? cts/blend-modes value)
|
||||
(ex/raise :type :assertion
|
||||
:code :data-validation
|
||||
:hint (str "unexpected blend mode: " value)))
|
||||
value))
|
||||
|
||||
(defn- svg-dimensions
|
||||
[{:keys [attrs] :as data}]
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
(sm/register! ::color-attrs schema:color-attrs)
|
||||
|
||||
(def check-color!
|
||||
(sm/check-fn schema:color))
|
||||
(sm/check-fn schema:color :hint "expected valid color struct"))
|
||||
|
||||
(def check-recent-color!
|
||||
(sm/check-fn schema:recent-color))
|
||||
|
|
|
@ -355,7 +355,8 @@
|
|||
(sm/check-fn schema:shape-attrs))
|
||||
|
||||
(def check-shape!
|
||||
(sm/check-fn schema:shape))
|
||||
(sm/check-fn schema:shape
|
||||
:hint "expected valid shape"))
|
||||
|
||||
(def valid-shape?
|
||||
(sm/lazy-validator schema:shape))
|
||||
|
|
|
@ -1725,8 +1725,8 @@
|
|||
[:images [:set :map]]
|
||||
[:position {:optional true} ::gpt/point]])
|
||||
|
||||
(def validate-paste-data!
|
||||
(sm/validate-fn schema:paste-data))
|
||||
(def paste-data-valid?
|
||||
(sm/lazy-validator schema:paste-data))
|
||||
|
||||
(defn- paste-transit
|
||||
[{:keys [images] :as pdata}]
|
||||
|
@ -1751,8 +1751,10 @@
|
|||
(let [file-id (:current-file-id state)
|
||||
features (features/get-team-enabled-features state)]
|
||||
|
||||
(validate-paste-data! pdata {:hint "invalid paste data"
|
||||
:code :invalid-paste-data})
|
||||
(when-not (paste-data-valid? pdata)
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-paste-data
|
||||
:hibt "invalid paste data found"))
|
||||
|
||||
(cfeat/check-paste-features! features (:features pdata))
|
||||
(if (= file-id (:file-id pdata))
|
||||
|
|
|
@ -465,16 +465,16 @@
|
|||
(defn change-color-in-selected
|
||||
[operations new-color old-color]
|
||||
|
||||
(dm/verify!
|
||||
"expected valid change color operations"
|
||||
(dm/assert!
|
||||
"expected valid color operations"
|
||||
(check-change-color-operations! operations))
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid color struct for new-color param"
|
||||
(dm/assert!
|
||||
"expected valid color structure"
|
||||
(ctc/check-color! new-color))
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid color struct for old-color param"
|
||||
(dm/assert!
|
||||
"expected valid color structure"
|
||||
(ctc/check-color! old-color))
|
||||
|
||||
(ptk/reify ::change-color-in-selected
|
||||
|
@ -498,7 +498,7 @@
|
|||
[color stroke?]
|
||||
|
||||
(dm/assert!
|
||||
"should be a valid color"
|
||||
"expected valid color structure"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(ptk/reify ::apply-color-from-palette
|
||||
|
|
|
@ -193,9 +193,17 @@
|
|||
|
||||
(defn rename-color
|
||||
[file-id id new-name]
|
||||
(dm/verify! (uuid? file-id))
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/verify! (string? new-name))
|
||||
(dm/assert!
|
||||
"expected valid uuid for `id`"
|
||||
(uuid? id))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid uuid for `file-id`"
|
||||
(uuid? file-id))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid string for `new-name`"
|
||||
(string? new-name))
|
||||
|
||||
(ptk/reify ::rename-color
|
||||
ptk/WatchEvent
|
||||
|
@ -243,8 +251,15 @@
|
|||
|
||||
(defn rename-media
|
||||
[id new-name]
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/verify! (string? new-name))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid uuid for `id`"
|
||||
(uuid? id))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid string for `new-name`"
|
||||
(string? new-name))
|
||||
|
||||
(ptk/reify ::rename-media
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
@ -261,8 +276,11 @@
|
|||
(rx/of (dch/commit-changes changes))))))))
|
||||
|
||||
(defn delete-media
|
||||
[{:keys [id] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
[{:keys [id]}]
|
||||
(dm/assert!
|
||||
"expected valid uuid for `id`"
|
||||
(uuid? id))
|
||||
|
||||
(ptk/reify ::delete-media
|
||||
ev/Event
|
||||
(-data [_] {:id id})
|
||||
|
@ -435,8 +453,14 @@
|
|||
(defn rename-component
|
||||
"Rename the component with the given id, in the current file library."
|
||||
[id new-name]
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/verify! (string? new-name))
|
||||
(dm/assert!
|
||||
"expected an uuid instance"
|
||||
(uuid? id))
|
||||
|
||||
(dm/assert!
|
||||
"expected string for new-name"
|
||||
(string? new-name))
|
||||
|
||||
(ptk/reify ::rename-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
@ -487,8 +511,11 @@
|
|||
|
||||
(defn delete-component
|
||||
"Delete the component with the given id, from the current file library."
|
||||
[{:keys [id] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
[{:keys [id]}]
|
||||
(dm/assert!
|
||||
"expected valid uuid for `id`"
|
||||
(uuid? id))
|
||||
|
||||
(ptk/reify ::delete-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
@ -1129,7 +1156,9 @@
|
|||
(defn touch-component
|
||||
"Update the modified-at attribute of the component to now"
|
||||
[id]
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/assert!
|
||||
"expected valid uuid for `id`"
|
||||
(uuid? id))
|
||||
(ptk/reify ::touch-component
|
||||
cljs.core/IDeref
|
||||
(-deref [_] [id])
|
||||
|
|
|
@ -98,8 +98,8 @@
|
|||
(add-shape shape {}))
|
||||
([shape {:keys [no-select? no-update-layout?]}]
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid shape"
|
||||
(dm/assert!
|
||||
"expected valid shape"
|
||||
(cts/check-shape! shape))
|
||||
|
||||
(ptk/reify ::add-shape
|
||||
|
|
Loading…
Add table
Reference in a new issue