From 19c5d32a89d6c31e490ea9c636d341404b62e5a5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sat, 11 Nov 2023 00:10:26 +0100 Subject: [PATCH] :sparkles: Improve efficiency and code style of file validate ns --- common/src/app/common/files/validate.cljc | 490 +++++++++++--------- common/src/app/common/types/pages_list.cljc | 4 +- 2 files changed, 268 insertions(+), 226 deletions(-) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index c690bf713c..7272e59c68 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -61,34 +61,37 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:dynamic *errors* nil) -(def ^:dynamic *throw-on-error* false) -(defn- report-error - [code msg shape file page & args] - (when (some? *errors*) - (if (true? *throw-on-error*) - (ex/raise {:type :validation - :code code - :hint msg - :args args - ::explain (str/format "file %s, page %s, shape %s" - (:id file) - (:id page) - (:id shape))}) - (vswap! *errors* conj {:code code - :hint msg - :shape shape - :file-id (:id file) - :page-id (:id page) - :args args})))) +(defn report-error! + [code hint shape file page & args] + (if (some? *errors*) + (vswap! *errors* conj {:code code + :hint hint + :shape shape + :file-id (:id file) + :page-id (:id page) + :args args}) + + (let [explain (str/ffmt "file %, page %, shape %" + (:id file) + (:id page) + (:id shape))] + (ex/raise :type :validation + :code code + :hint hint + :args args + :file-id (:id file) + :page-id (:id page) + :shape-id (:id shape) + ::explain explain)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; VALIDATION FUNCTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare validate-shape) +(declare validate-shape!) -(defn validate-geometry +(defn validate-geometry! "Validate that the shape has valid coordinates, selrect and points." [shape file page] (when (and (not (#{:path :bool} (:type shape))) @@ -98,318 +101,357 @@ (nil? (:height shape)) (nil? (:selrect shape)) (nil? (:points shape)))) - (report-error :invalid-geometry - (str/format "Shape greometry is invalid") - shape file page))) + (report-error! :invalid-geometry + "Shape greometry is invalid" + shape file page))) -(defn validate-parent-children +(defn validate-parent-children! "Validate parent and children exists, and the link is bidirectional." [shape file page] (let [parent (ctst/get-shape page (:parent-id shape))] (if (nil? parent) - (report-error :parent-not-found - (str/format "Parent %s not found" (:parent-id shape)) - shape file page) + (report-error! :parent-not-found + (str/ffmt "Parent % not found" (:parent-id shape)) + shape file page) (do (when-not (cph/root? shape) (when-not (some #{(:id shape)} (:shapes parent)) - (report-error :child-not-in-parent - (str/format "Shape %s not in parent's children list" (:id shape)) - shape file page))) + (report-error! :child-not-in-parent + (str/ffmt "Shape % not in parent's children list" (:id shape)) + shape file page))) (doseq [child-id (:shapes shape)] (when (nil? (ctst/get-shape page child-id)) - (report-error :child-not-found - (str/format "Child %s not found" child-id) - shape file page - :child-id child-id))))))) + (report-error! :child-not-found + (str/ffmt "Child % not found" child-id) + shape file page + :child-id child-id))))))) -(defn validate-frame - "Validate that the frame-id shape exists and is indeed a frame. Also it must point to the - parent shape (if this is a frame) or to the frame-id of the parent (if not)." +(defn validate-frame! + "Validate that the frame-id shape exists and is indeed a frame. Also + it must point to the parent shape (if this is a frame) or to the + frame-id of the parent (if not)." [shape file page] (let [frame (ctst/get-shape page (:frame-id shape))] (if (nil? frame) - (report-error :frame-not-found - (str/format "Frame %s not found" (:frame-id shape)) - shape file page) + (report-error! :frame-not-found + (str/ffmt "Frame % not found" (:frame-id shape)) + shape file page) (if (not= (:type frame) :frame) - (report-error :invalid-frame - (str/format "Frame %s is not actually a frame" (:frame-id shape)) - shape file page) + (report-error! :invalid-frame + (str/ffmt "Frame % is not actually a frame" (:frame-id shape)) + shape file page) (let [parent (ctst/get-shape page (:parent-id shape))] (when (some? parent) (if (= (:type parent) :frame) (when-not (= (:frame-id shape) (:id parent)) - (report-error :invalid-frame - (str/format "Frame-id should point to parent" (:id parent)) - shape file page)) + (report-error! :invalid-frame + (str/ffmt "Frame-id should point to parent %" (:id parent)) + shape file page)) (when-not (= (:frame-id shape) (:frame-id parent)) - (report-error :invalid-frame - (str/format "Frame-id should point to parent frame" (:frame-id parent)) - shape file page))))))))) + (report-error! :invalid-frame + (str/ffmt "Frame-id should point to parent frame %" (:frame-id parent)) + shape file page))))))))) -(defn validate-component-main-head - "Validate shape is a main instance head, component exists and its main-instance points to this shape." +(defn validate-component-main-head! + "Validate shape is a main instance head, component exists + and its main-instance points to this shape." [shape file page libraries] (when (nil? (:main-instance shape)) - (report-error :component-not-main - (str/format "Shape expected to be main instance") - shape file page)) + (report-error! :component-not-main + "Shape expected to be main instance" + shape file page)) (when-not (= (:component-file shape) (:id file)) - (report-error :component-main-external - (str/format "Main instance should refer to a component in the same file") - shape file page)) + (report-error! :component-main-external + "Main instance should refer to a component in the same file" + shape file page)) (let [component (ctf/resolve-component shape file libraries :include-deleted? true)] (if (nil? component) - (report-error :component-not-found - (str/format "Component %s not found in file" (:component-id shape) (:component-file shape)) - shape file page) + (report-error! :component-not-found + (str/ffmt "Component % not found in file %" (:component-id shape) (:component-file shape)) + shape file page) (do (when-not (= (:main-instance-id component) (:id shape)) - (report-error :invalid-main-instance-id - (str/format "Main instance id of component %s is not valid" (:component-id shape)) - shape file page)) + (report-error! :invalid-main-instance-id + (str/ffmt "Main instance id of component % is not valid" (:component-id shape)) + shape file page)) (when-not (= (:main-instance-page component) (:id page)) - (report-error :invalid-main-instance-page - (str/format "Main instance page of component %s is not valid" (:component-id shape)) - shape file page)))))) + (report-error! :invalid-main-instance-page + (str/ffmt "Main instance page of component % is not valid" (:component-id shape)) + shape file page)))))) -(defn validate-component-not-main-head - "Validate shape is a not-main instance head, component exists and its main-instance does not point to this shape." +(defn validate-component-not-main-head! + "Validate shape is a not-main instance head, component + exists and its main-instance does not point to this + shape." [shape file page libraries] (when (some? (:main-instance shape)) - (report-error :component-not-main - (str/format "Shape not expected to be main instance") - shape file page)) + (report-error! :component-not-main + "Shape not expected to be main instance" + shape file page)) + (let [component (ctf/resolve-component shape file libraries {:include-deleted? true})] (if (nil? component) - (report-error :component-not-found - (str/format "Component %s not found in file" (:component-id shape) (:component-file shape)) - shape file page) - (do - (when (and (= (:main-instance-id component) (:id shape)) - (= (:main-instance-page component) (:id page))) - (report-error :invalid-main-instance - (str/format "Main instance of component %s should not be this shape" (:id component)) - shape file page)))))) + (report-error! :component-not-found + (str/ffmt "Component % not found in file %" (:component-id shape) (:component-file shape)) + shape file page) + (when (and (= (:main-instance-id component) (:id shape)) + (= (:main-instance-page component) (:id page))) + (report-error! :invalid-main-instance + (str/ffmt "Main instance of component % should not be this shape" (:id component)) + shape file page))))) -(defn validate-component-not-main-not-head +(defn validate-component-not-main-not-head! "Validate that this shape is not main instance and not head." [shape file page] (when (some? (:main-instance shape)) - (report-error :component-main - (str/format "Shape not expected to be main instance") - shape file page)) + (report-error! :component-main + "Shape not expected to be main instance" + shape file page)) (when (or (some? (:component-id shape)) (some? (:component-file shape))) - (report-error :component-main - (str/format "Shape not expected to be component head") - shape file page))) + (report-error! :component-main + "Shape not expected to be component head" + shape file page))) -(defn validate-component-root +(defn validate-component-root! "Validate that this shape is an instance root." [shape file page] (when (nil? (:component-root shape)) - (report-error :should-be-component-root - (str/format "Shape should be component root") - shape file page))) + (report-error! :should-be-component-root + "Shape should be component root" + shape file page))) -(defn validate-component-not-root +(defn validate-component-not-root! "Validate that this shape is not an instance root." [shape file page] (when (some? (:component-root shape)) - (report-error :should-not-be-component-root - (str/format "Shape should not be component root") - shape file page))) + (report-error! :should-not-be-component-root + "Shape should not be component root" + shape file page))) -(defn validate-component-ref +(defn validate-component-ref! "Validate that the referenced shape exists in the near component." [shape file page libraries] (let [ref-shape (ctf/find-ref-shape file page libraries shape :include-deleted? true)] (when (nil? ref-shape) - (report-error :ref-shape-not-found - (str/format "Referenced shape %s not found in near component" (:shape-ref shape)) - shape file page)))) + (report-error! :ref-shape-not-found + (str/ffmt "Referenced shape % not found in near component" (:shape-ref shape)) + shape file page)))) -(defn validate-component-not-ref +(defn validate-component-not-ref! "Validate that this shape does not reference other one." [shape file page] (when (some? (:shape-ref shape)) - (report-error :shape-ref-in-main - (str/format "Shape inside main instance should not have shape-ref") - shape file page))) + (report-error! :shape-ref-in-main + "Shape inside main instance should not have shape-ref" + shape file page))) -(defn validate-shape-main-root-top - "Root shape of a top main instance - :main-instance - :component-id - :component-file - :component-root" +(defn validate-shape-main-root-top! + "Root shape of a top main instance: + + - :main-instance + - :component-id + - :component-file + - :component-root" [shape file page libraries] - (validate-component-main-head shape file page libraries) - (validate-component-root shape file page) - (validate-component-not-ref shape file page) + (validate-component-main-head! shape file page libraries) + (validate-component-root! shape file page) + (validate-component-not-ref! shape file page) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :main-top))) + (validate-shape! child-id file page libraries :context :main-top))) -(defn validate-shape-main-root-nested +(defn validate-shape-main-root-nested! "Root shape of a nested main instance - :main-instance - :component-id - :component-file" + - :main-instance + - :component-id + - :component-file" [shape file page libraries] - (validate-component-main-head shape file page libraries) - (validate-component-not-root shape file page) - (validate-component-not-ref shape file page) + (validate-component-main-head! shape file page libraries) + (validate-component-not-root! shape file page) + (validate-component-not-ref! shape file page) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :main-nested))) + (validate-shape! child-id file page libraries :context :main-nested))) -(defn validate-shape-copy-root-top +(defn validate-shape-copy-root-top! "Root shape of a top copy instance - :component-id - :component-file - :component-root - :shape-ref" + - :component-id + - :component-file + - :component-root + - :shape-ref" [shape file page libraries] - (validate-component-not-main-head shape file page libraries) - (validate-component-root shape file page) - (validate-component-ref shape file page libraries) + (validate-component-not-main-head! shape file page libraries) + (validate-component-root! shape file page) + (validate-component-ref! shape file page libraries) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :copy-top))) + (validate-shape! child-id file page libraries :context :copy-top))) -(defn validate-shape-copy-root-nested +(defn validate-shape-copy-root-nested! "Root shape of a nested copy instance - :component-id - :component-file - :shape-ref" + - :component-id + - :component-file + - :shape-ref" [shape file page libraries] - (validate-component-not-main-head shape file page libraries) - (validate-component-not-root shape file page) - (validate-component-ref shape file page libraries) + (validate-component-not-main-head! shape file page libraries) + (validate-component-not-root! shape file page) + (validate-component-ref! shape file page libraries) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :copy-nested))) + (validate-shape! child-id file page libraries :context :copy-nested))) -(defn validate-shape-main-not-root - "Not-root shape of a main instance - (not any attribute)" +(defn validate-shape-main-not-root! + "Not-root shape of a main instance (not any attribute)" [shape file page libraries] - (validate-component-not-main-not-head shape file page) - (validate-component-not-root shape file page) - (validate-component-not-ref shape file page) + (validate-component-not-main-not-head! shape file page) + (validate-component-not-root! shape file page) + (validate-component-not-ref! shape file page) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :main-any))) + (validate-shape! child-id file page libraries :context :main-any))) -(defn validate-shape-copy-not-root - "Not-root shape of a copy instance - :shape-ref" +(defn validate-shape-copy-not-root! + "Not-root shape of a copy instance :shape-ref" [shape file page libraries] - (validate-component-not-main-not-head shape file page) - (validate-component-not-root shape file page) - (validate-component-ref shape file page libraries) + (validate-component-not-main-not-head! shape file page) + (validate-component-not-root! shape file page) + (validate-component-ref! shape file page libraries) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :copy-any))) + (validate-shape! child-id file page libraries :context :copy-any))) -(defn validate-shape-not-component - "Shape is not in a component or is a fostered children - (not any attribute)" +(defn validate-shape-not-component! + "Shape is not in a component or is a fostered children (not any + attribute)" [shape file page libraries] - (validate-component-not-main-not-head shape file page) - (validate-component-not-root shape file page) - (validate-component-not-ref shape file page) + (validate-component-not-main-not-head! shape file page) + (validate-component-not-root! shape file page) + (validate-component-not-ref! shape file page) (doseq [child-id (:shapes shape)] - (validate-shape child-id file page libraries :context :not-component))) + (validate-shape! child-id file page libraries :context :not-component))) -(defn validate-shape - "Validate referential integrity and semantic coherence of a shape and all its children. +(defn validate-shape! + "Validate referential integrity and semantic coherence of + a shape and all its children. Raises an exception on first + error found. - The context is the situation of the parent in respect to components: - :not-component - :main-top - :main-nested - :copy-top - :copy-nested - :main-any - :copy-any" - [shape-id file page libraries & {:keys [context throw?] - :or {context :not-component - throw? nil}}] - (binding [*throw-on-error* (if (some? throw?) throw? *throw-on-error*) - *errors* (or *errors* (volatile! []))] - (let [shape (ctst/get-shape page shape-id)] + The context is the situation of the parent in respect to components: + - :not-component + - :main-top + - :main-nested + - :copy-top + - :copy-nested + - :main-any + - :copy-any + " + [shape-id file page libraries & {:keys [context] :or {context :not-component}}] + (let [shape (ctst/get-shape page shape-id)] - ; If this happens it's a bug in this validate functions - (dm/verify! (str/format "Shape %s not found" shape-id) (some? shape)) + ;; If this happens it's a bug in this validate functions + (dm/verify! + ["Shape % not found" shape-id] + (some? shape)) - (validate-geometry shape file page) - (validate-parent-children shape file page) - (validate-frame shape file page) - - (validate-parent-children shape file page) - (validate-frame shape file page) + (validate-geometry! shape file page) + (validate-parent-children! shape file page) + (validate-frame! shape file page) (if (ctk/instance-head? shape) (if (not= :frame (:type shape)) - (report-error :instance-head-not-frame - (str/format "Instance head should be a frame") - shape file page) + (report-error! :instance-head-not-frame + "Instance head should be a frame" + shape file page) (if (ctk/instance-root? shape) (if (ctk/main-instance? shape) (if (not= context :not-component) - (report-error :root-main-not-allowed - (str/format "Root main component not allowed inside other component") - shape file page) - (validate-shape-main-root-top shape file page libraries)) + (report-error! :root-main-not-allowed + "Root main component not allowed inside other component" + shape file page) + (validate-shape-main-root-top! shape file page libraries)) (if (not= context :not-component) - (report-error :root-copy-not-allowed - (str/format "Root copy component not allowed inside other component") - shape file page) - (validate-shape-copy-root-top shape file page libraries))) + (report-error! :root-copy-not-allowed + "Root copy component not allowed inside other component" + shape file page) + (validate-shape-copy-root-top! shape file page libraries))) (if (ctk/main-instance? shape) (if (= context :not-component) - (report-error :nested-main-not-allowed - (str/format "Nested main component only allowed inside other component") - shape file page) - (validate-shape-main-root-nested shape file page libraries)) + (report-error! :nested-main-not-allowed + "Nested main component only allowed inside other component" + shape file page) + (validate-shape-main-root-nested! shape file page libraries)) (if (= context :not-component) - (report-error :nested-copy-not-allowed - (str/format "Nested copy component only allowed inside other component") - shape file page) - (validate-shape-copy-root-nested shape file page libraries))))) + (report-error! :nested-copy-not-allowed + "Nested copy component only allowed inside other component" + shape file page) + (validate-shape-copy-root-nested! shape file page libraries))))) (if (ctk/in-component-copy? shape) (if-not (#{:copy-top :copy-nested :copy-any} context) - (report-error :not-head-copy-not-allowed - (str/format "Non-root copy only allowed inside a copy") - shape file page) - (validate-shape-copy-not-root shape file page libraries)) + (report-error! :not-head-copy-not-allowed + "Non-root copy only allowed inside a copy" + shape file page) + (validate-shape-copy-not-root! shape file page libraries)) (if (ctn/inside-component-main? (:objects page) shape) (if-not (#{:main-top :main-nested :main-any} context) - (report-error :not-head-main-not-allowed - (str/format "Non-root main only allowed inside a main component") - shape file page) - (validate-shape-main-not-root shape file page libraries)) + (report-error! :not-head-main-not-allowed + "Non-root main only allowed inside a main component" + shape file page) + (validate-shape-main-not-root! shape file page libraries)) (if (#{:main-top :main-nested :main-any} context) - (report-error :not-component-not-allowed - (str/format "Not compoments are not allowed inside a main") - shape file page) - (validate-shape-not-component shape file page libraries))))) + (report-error! :not-component-not-allowed + "Not compoments are not allowed inside a main" + shape file page) + (validate-shape-not-component! shape file page libraries))))))) - (deref *errors*)))) +(defn validate-shape + "Validate referential integrity and semantic coherence of + a shape and all its children. Returns a list of errors." + [shape-id file page libraries] + (binding [*errors* (volatile! [])] + (validate-shape! shape-id file page libraries) + (deref *errors*))) + +(def valid-fdata? + "Structural validation of file data using defined schema" + (sm/lazy-validator ::ctf/data)) + +(def get-fdata-explain + "Get schema explain data for file data" + (sm/lazy-explainer ::ctf/data)) + +(defn validate-file! + "Validate file data structure. + + If libraries are provided, then a full referential integrity and + semantic coherence check will be performed on all content of the + file. + + Raises a validation exception on first error found." + + ([file] (validate-file! file nil)) + ([{:keys [id data] :as file} libraries] + (when-not (valid-fdata? data) + (ex/raise :type :validation + :code :data-validation + :hint (str/ffmt "invalid file data found on file '%'" id) + :file-id id + ::sm/explain (get-fdata-explain data))) + + ;; If `libraries` is provided, this means the fill file + ;; validation is activated so we proceed to execute the + ;; validation + (when (seq libraries) + (doseq [page (filter :id (ctpl/pages-seq data))] + (validate-shape! uuid/zero file page libraries))) + + file)) (defn validate-file - "Validate referencial integrity and semantic coherence of all contents of a file." - [file libraries & {:keys [throw?] :or {throw? false}}] - (binding [*throw-on-error* throw? - *errors* (volatile! [])] - (->> (ctpl/pages-seq (:data file)) - (filter #(some? (:id %))) - (run! #(validate-shape uuid/zero file % libraries :throw? throw?))) - + "Validate referencial integrity and semantic coherence of + all contents of a file. Returns a list of errors." + [file libraries] + (binding [*errors* (volatile! [])] + (validate-file! file libraries) (deref *errors*))) diff --git a/common/src/app/common/types/pages_list.cljc b/common/src/app/common/types/pages_list.cljc index ad2019e134..1a1dbb5664 100644 --- a/common/src/app/common/types/pages_list.cljc +++ b/common/src/app/common/types/pages_list.cljc @@ -33,8 +33,8 @@ (update :pages-index assoc id (dissoc page :index)))) (defn pages-seq - [file-data] - (vals (:pages-index file-data))) + [fdata] + (vals (:pages-index fdata))) (defn update-page [file-data page-id f]