mirror of
https://github.com/penpot/penpot.git
synced 2025-04-06 12:01:19 -05:00
✨ Add validate & repair functions
This commit is contained in:
parent
973affb259
commit
836b4538dd
13 changed files with 851 additions and 335 deletions
|
@ -139,16 +139,17 @@
|
|||
data (some-> params :file :path io/read-as-bytes)]
|
||||
|
||||
(if (and data project-id)
|
||||
(let [fname (str "Imported file *: " (dt/now))
|
||||
overwrite? (contains? params :overwrite?)
|
||||
file-id (or (and overwrite? (ex/ignoring (-> params :file :filename parse-uuid)))
|
||||
(uuid/next))]
|
||||
(let [fname (str "Imported file *: " (dt/now))
|
||||
reuse-id? (contains? params :reuseid)
|
||||
file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid)))
|
||||
(uuid/next))]
|
||||
|
||||
(if (and overwrite? file-id
|
||||
(if (and reuse-id? file-id
|
||||
(is-file-exists? pool file-id))
|
||||
(do
|
||||
(db/update! pool :file
|
||||
{:data data}
|
||||
{:data data
|
||||
:deleted-at nil}
|
||||
{:id file-id})
|
||||
{::yrs/status 200
|
||||
::yrs/body "OK UPDATED"})
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
(declare send-notifications!)
|
||||
(declare update-file)
|
||||
(declare update-file*)
|
||||
(declare update-file-data)
|
||||
(declare take-snapshot?)
|
||||
|
||||
;; If features are specified from params and the final feature
|
||||
|
@ -208,26 +209,6 @@
|
|||
:project-id (:project-id file)
|
||||
:team-id (:team-id file)}))))))
|
||||
|
||||
(defn- update-file-data
|
||||
[file changes]
|
||||
(-> file
|
||||
(update :revn inc)
|
||||
(update :data (fn [data]
|
||||
(cond-> data
|
||||
:always
|
||||
(-> (blob/decode)
|
||||
(assoc :id (:id file))
|
||||
(pmg/migrate-data))
|
||||
|
||||
(and (contains? ffeat/*current* "components/v2")
|
||||
(not (contains? ffeat/*previous* "components/v2")))
|
||||
(ctf/migrate-to-components-v2)
|
||||
|
||||
:always
|
||||
(-> (cp/process-changes changes)
|
||||
(blob/encode)))))))
|
||||
|
||||
|
||||
(defn- update-file*
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id file changes session-id ::created-at] :as params}]
|
||||
(let [;; Process the file data in the CLIMIT context; scheduling it
|
||||
|
@ -267,6 +248,25 @@
|
|||
;; Retrieve and return lagged data
|
||||
(get-lagged-changes conn params))))
|
||||
|
||||
(defn- update-file-data
|
||||
[file changes]
|
||||
(-> file
|
||||
(update :revn inc)
|
||||
(update :data (fn [data]
|
||||
(cond-> data
|
||||
:always
|
||||
(-> (blob/decode)
|
||||
(assoc :id (:id file))
|
||||
(pmg/migrate-data))
|
||||
|
||||
(and (contains? ffeat/*current* "components/v2")
|
||||
(not (contains? ffeat/*previous* "components/v2")))
|
||||
(ctf/migrate-to-components-v2)
|
||||
|
||||
:always
|
||||
(-> (cp/process-changes changes)
|
||||
(blob/encode)))))))
|
||||
|
||||
(defn- take-snapshot?
|
||||
"Defines the rule when file `data` snapshot should be saved."
|
||||
[{:keys [revn modified-at] :as file}]
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"A collection of adhoc fixes scripts."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.validate :as cfv]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pages.helpers :as cph]
|
||||
|
@ -29,7 +30,7 @@
|
|||
(d/index-by :id))
|
||||
|
||||
update-page (fn [page]
|
||||
(let [errors (ctf/validate-shape uuid/zero file page libs)]
|
||||
(let [errors (cfv/validate-shape uuid/zero file page libs)]
|
||||
(when (seq errors)
|
||||
(println "******Errors in file " (:id file) " page " (:id page))
|
||||
(pprint errors {:level 3}))))]
|
||||
|
|
368
common/src/app/common/files/repair.cljc
Normal file
368
common/src/app/common/files/repair.cljc
Normal file
|
@ -0,0 +1,368 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.files.repair
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(log/set-level! :debug)
|
||||
|
||||
(defmulti repair-error
|
||||
(fn [code _error _file-data _libraries] code))
|
||||
|
||||
(defmethod repair-error :parent-not-found
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Set parent to root frame.
|
||||
(log/debug :hint " -> Set to " :parent-id uuid/zero)
|
||||
(assoc shape :parent-id uuid/zero))]
|
||||
|
||||
(log/info :hint "Repairing shape :parent-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :child-not-in-parent
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [parent-shape]
|
||||
; Add shape to parent's children list
|
||||
(log/debug :hint " -> Add children to" :parent-id (:id parent-shape))
|
||||
(update parent-shape :shapes conj (:id shape)))]
|
||||
|
||||
(log/info :hint "Repairing shape :child-not-in-parent" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:parent-id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :child-not-found
|
||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [parent-shape]
|
||||
; Remove child shape from children list
|
||||
(log/debug :hint " -> Remove child " :child-id (:child-id args))
|
||||
(update parent-shape :shapes d/removev #(= % (:child-id args))))]
|
||||
|
||||
(log/info :hint "Repairing shape :child-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :frame-not-found
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Locate the first frame in parents and set frame-id to it.
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
frame (cph/get-frame (:objects page) (:parent-id shape))
|
||||
frame-id (or (:id frame) uuid/zero)]
|
||||
(log/debug :hint " -> Set to " :frame-id frame-id)
|
||||
(assoc shape :frame-id frame-id)))]
|
||||
|
||||
(log/info :hint "Repairing shape :frame-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :invalid-frame
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Locate the first frame in parents and set frame-id to it.
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
frame (cph/get-frame (:objects page) (:parent-id shape))
|
||||
frame-id (or (:id frame) uuid/zero)]
|
||||
(log/debug :hint " -> Set to " :frame-id frame-id)
|
||||
(assoc shape :frame-id frame-id)))]
|
||||
|
||||
(log/info :hint "Repairing shape :invalid-frame" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-not-main
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Set the :shape as main instance root
|
||||
(log/debug :hint " -> Set :main-instance")
|
||||
(assoc shape :main-instance true))]
|
||||
|
||||
(log/info :hint "Repairing shape :component-not-main" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-main-external
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; There is no solution that may recover it with confidence
|
||||
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||
shape)]
|
||||
|
||||
(log/info :hint "Repairing shape :component-main-external" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-not-found
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
shape-ids (cph/get-children-ids-with-self (:objects page) (:id shape))
|
||||
|
||||
repair-shape
|
||||
(fn [shape]
|
||||
; Detach the shape and convert it to non instance.
|
||||
(log/debug :hint " -> Detach shape" :shape-id (:id shape))
|
||||
(ctk/detach-shape shape))]
|
||||
|
||||
(log/info :hint "Repairing shape :component-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes shape-ids repair-shape))))
|
||||
|
||||
(defmethod repair-error :invalid-main-instance-id
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-component
|
||||
(fn [component]
|
||||
; Assign main instance in the component to current shape
|
||||
(log/debug :hint " -> Assign main-instance-id" :component-id (:id component))
|
||||
(assoc component :main-instance-id (:id shape)))]
|
||||
(log/info :hint "Repairing shape :invalid-main-instance-id" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component [(:component-id shape)] repair-component))))
|
||||
|
||||
(defmethod repair-error :invalid-main-instance-page
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-component
|
||||
(fn [component]
|
||||
; Assign main instance in the component to current shape
|
||||
(log/debug :hint " -> Assign main-instance-page" :component-id (:id component))
|
||||
(assoc component :main-instance-page page-id))]
|
||||
(log/info :hint "Repairing shape :invalid-main-instance-page" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component [(:component-id shape)] repair-component))))
|
||||
|
||||
(defmethod repair-error :invalid-main-instance
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; There is no solution that may recover it with confidence
|
||||
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||
shape)]
|
||||
|
||||
(log/info :hint "Repairing shape :invalid-main-instance" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-main
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Unset the :shape as main instance root
|
||||
(log/debug :hint " -> Unset :main-instance")
|
||||
(dissoc shape :main-instance))]
|
||||
|
||||
(log/info :hint "Repairing shape :component-main" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :should-be-component-root
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a top copy root.
|
||||
(log/debug :hint " -> Set :component-root")
|
||||
(assoc shape :component-root true))]
|
||||
|
||||
(log/info :hint "Repairing shape :should-be-component-root" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :should-not-be-component-root
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a nested copy root.
|
||||
(log/debug :hint " -> Unset :component-root")
|
||||
(dissoc shape :component-root))]
|
||||
|
||||
(log/info :hint "Repairing shape :should-not-be-component-root" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :ref-shape-not-found
|
||||
[_ {:keys [shape page-id] :as error} file-data libraries]
|
||||
(let [matching-shape (let [page (ctpl/get-page file-data page-id)
|
||||
root-shape (ctn/get-component-shape (:objects page) shape)
|
||||
component-file (if (= (:component-file root-shape) (:id file-data))
|
||||
file-data
|
||||
(-> (get libraries (:component-file root-shape)) :data))
|
||||
component (when component-file
|
||||
(ctkl/get-component (:data component-file) (:component-id root-shape) true))
|
||||
shapes (ctf/get-component-shapes file-data component)]
|
||||
(d/seek #(= (:shape-ref %) (:shape-ref shape)) shapes))
|
||||
|
||||
reassign-shape
|
||||
(fn [shape]
|
||||
(log/debug :hint " -> Reassign shape-ref to" :shape-ref (:id matching-shape))
|
||||
(assoc shape :shape-ref (:id matching-shape)))
|
||||
|
||||
detach-shape
|
||||
(fn [shape]
|
||||
(log/debug :hint " -> Detach shape" :shape-id (:id shape))
|
||||
(ctk/detach-shape shape))]
|
||||
|
||||
; If the shape still refers to the remote component, try to find the corresponding near one
|
||||
; and link to it. If not, detach the shape.
|
||||
(log/info :hint "Repairing shape :ref-shape-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(if (some? matching-shape)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] reassign-shape))
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
shape-ids (cph/get-children-ids-with-self (:objects page) (:id shape))]
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes shape-ids detach-shape))))))
|
||||
|
||||
(defmethod repair-error :shape-ref-in-main
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Remove shape-ref
|
||||
(log/debug :hint " -> Unset :shape-ref")
|
||||
(dissoc shape :shape-ref))]
|
||||
|
||||
(log/info :hint "Repairing shape :shape-ref-in-main" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :root-main-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a nested main head.
|
||||
(log/debug :hint " -> Unset :component-root")
|
||||
(dissoc shape :component-root))]
|
||||
|
||||
(log/info :hint "Repairing shape :root-main-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :nested-main-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a top main head.
|
||||
(log/debug :hint " -> Set :component-root")
|
||||
(assoc shape :component-root true))]
|
||||
|
||||
(log/info :hint "Repairing shape :nested-main-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :root-copy-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a nested copy head.
|
||||
(log/debug :hint " -> Unset :component-root")
|
||||
(dissoc shape :component-root))]
|
||||
|
||||
(log/info :hint "Repairing shape :root-copy-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :nested-copy-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Convert the shape in a top copy root.
|
||||
(log/debug :hint " -> Set :component-root")
|
||||
(assoc shape :component-root true))]
|
||||
|
||||
(log/info :hint "Repairing shape :nested-copy-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :not-head-main-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Detach the shape and convert it to non instance.
|
||||
(log/debug :hint " -> Detach shape" :shape-id (:id shape))
|
||||
(ctk/detach-shape shape))]
|
||||
|
||||
(log/info :hint "Repairing shape :not-head-main-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :not-head-copy-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Detach the shape and convert it to non instance.
|
||||
(log/debug :hint " -> Detach shape" :shape-id (:id shape))
|
||||
(ctk/detach-shape shape))]
|
||||
|
||||
(log/info :hint "Repairing shape :not-head-copy-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :not-component-not-allowed
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; There is no solution that may recover it with confidence
|
||||
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||
shape)]
|
||||
|
||||
(log/info :hint "Repairing shape :not-component-not-allowed" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :default
|
||||
[_ error file _]
|
||||
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error))
|
||||
file)
|
||||
|
||||
(defn repair-file
|
||||
[file-data libraries errors]
|
||||
(log/info :hint "Repairing file" :id (:id file-data) :error-count (count errors))
|
||||
(reduce (fn [changes error]
|
||||
(pcb/concat-changes changes
|
||||
(repair-error (:code error)
|
||||
error
|
||||
file-data
|
||||
libraries)))
|
||||
(pcb/empty-changes nil)
|
||||
errors))
|
380
common/src/app/common/files/validate.cljc
Normal file
380
common/src/app/common/files/validate.cljc
Normal file
|
@ -0,0 +1,380 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.files.validate
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def error-codes
|
||||
#{:parent-not-found
|
||||
:child-not-in-parent
|
||||
:child-not-found
|
||||
:frame-not-found
|
||||
:invalid-frame
|
||||
:component-not-main
|
||||
:component-main-external
|
||||
:component-not-found
|
||||
:invalid-main-instance-id
|
||||
:invalid-main-instance-page
|
||||
:invalid-main-instance
|
||||
:component-main
|
||||
:should-be-component-root
|
||||
:should-not-be-component-root
|
||||
:ref-shape-not-found
|
||||
:shape-ref-in-main
|
||||
:root-main-not-allowed
|
||||
:nested-main-not-allowed
|
||||
:root-copy-not-allowed
|
||||
:nested-copy-not-allowed
|
||||
:not-head-main-not-allowed
|
||||
:not-head-copy-not-allowed
|
||||
:not-component-not-allowed})
|
||||
|
||||
(def validation-error
|
||||
[:map {:title "ValidationError"}
|
||||
[:code {:optional false} [::sm/one-of error-codes]]
|
||||
[:hint {:optional false} :string]
|
||||
[:shape {:optional true} :map] ; Cannot validate a shape because here it may be broken
|
||||
[:file-id ::sm/uuid]
|
||||
[:page-id ::sm/uuid]])
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ERROR HANDLING
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(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\npage %s\nshape %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}))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; VALIDATION FUNCTIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare validate-shape)
|
||||
|
||||
(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)
|
||||
(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)))
|
||||
|
||||
(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)))))))
|
||||
|
||||
(defn validate-frame
|
||||
"Validate that the frame-id shape exists and is indeed a frame."
|
||||
[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)
|
||||
(when (not= (:type frame) :frame)
|
||||
(report-error :invalid-frame
|
||||
(str/format "Frame %s is not actually a frame" (:frame-id shape))
|
||||
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."
|
||||
[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))
|
||||
(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))
|
||||
(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-not (= (:main-instance-id component) (:id shape))
|
||||
(report-error :nvalid-main-instance-id
|
||||
(str/format "Main instance id of component %s 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))))))
|
||||
|
||||
(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))
|
||||
(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))))))
|
||||
|
||||
(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))
|
||||
(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)))
|
||||
|
||||
(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)))
|
||||
|
||||
(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)))
|
||||
|
||||
(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))))
|
||||
|
||||
(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)))
|
||||
|
||||
(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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-top :clear-errors? false)))
|
||||
|
||||
(defn validate-shape-main-root-nested
|
||||
"Root shape of a nested main instance
|
||||
: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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-nested :clear-errors? false)))
|
||||
|
||||
(defn validate-shape-copy-root-top
|
||||
"Root shape of a top copy instance
|
||||
: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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-top :clear-errors? false)))
|
||||
|
||||
(defn validate-shape-copy-root-nested
|
||||
"Root shape of a nested copy instance
|
||||
: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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-nested :clear-errors? false)))
|
||||
|
||||
(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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-any :clear-errors? false)))
|
||||
|
||||
(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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-any :clear-errors? false)))
|
||||
|
||||
(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)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :not-component :clear-errors? false)))
|
||||
|
||||
(defn validate-shape
|
||||
"Validate referential integrity and semantic coherence of a shape and all its children.
|
||||
|
||||
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? false}}]
|
||||
(binding [*throw-on-error* throw?
|
||||
*errors* (or *errors* (volatile! []))]
|
||||
(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))
|
||||
|
||||
(validate-parent-children shape file page)
|
||||
(validate-frame shape file page)
|
||||
|
||||
(if (ctk/main-instance? shape)
|
||||
|
||||
(if (ctk/instance-root? 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))
|
||||
|
||||
(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)))
|
||||
|
||||
(if (ctk/instance-head? shape)
|
||||
|
||||
(if (ctk/instance-root? shape)
|
||||
(if (not= context :not-component)
|
||||
(report-error :root-copy-not-allowed
|
||||
(str/format "Root copy not allowed inside other component")
|
||||
shape file page)
|
||||
(validate-shape-copy-root-top shape file page libraries))
|
||||
|
||||
(if (= context :not-component)
|
||||
(report-error :nested-copy-not-allowed
|
||||
(str/format "Nested copy only allowed inside other component")
|
||||
shape file page)
|
||||
(validate-shape-copy-root-nested shape file page libraries)))
|
||||
|
||||
(if (ctn/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))
|
||||
|
||||
(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))
|
||||
|
||||
(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))))))
|
||||
|
||||
(deref *errors*))))
|
||||
|
||||
(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))
|
||||
(run! #(validate-shape uuid/zero file % libraries :throw? throw?)))
|
||||
|
||||
(deref *errors*)))
|
|
@ -76,7 +76,6 @@
|
|||
[:index {:optional true} [:maybe :int]]
|
||||
[:ignore-touched {:optional true} :boolean]]]
|
||||
|
||||
|
||||
[:mod-obj
|
||||
[:map {:title "ModObjChange"}
|
||||
[:type [:= :mod-obj]]
|
||||
|
|
|
@ -82,6 +82,15 @@
|
|||
::file-data fdata
|
||||
::applied-changes-count 0)))
|
||||
|
||||
(defn with-file-data
|
||||
[changes fdata]
|
||||
(let [page-id (::page-id (meta changes))
|
||||
fdata (assoc-in fdata [:pages-index uuid/zero]
|
||||
(get-in fdata [:pages-index page-id]))]
|
||||
(vary-meta changes assoc
|
||||
::file-data fdata
|
||||
::applied-changes-count 0)))
|
||||
|
||||
(defn with-library-data
|
||||
[changes data]
|
||||
(vary-meta changes assoc
|
||||
|
@ -711,14 +720,18 @@
|
|||
:id id
|
||||
:name (:name new-component)
|
||||
:path (:path new-component)
|
||||
:main-instance-id (:main-instance-id new-component)
|
||||
:main-instance-page (:main-instance-page new-component)
|
||||
:annotation (:annotation new-component)
|
||||
:objects (:objects new-component)}) ;; this won't exist in components-v2
|
||||
(update :undo-changes conj {:type :mod-component
|
||||
:id id
|
||||
:name (:name prev-component)
|
||||
:path (:path prev-component)
|
||||
:annotation (:annotation prev-component)
|
||||
:objects (:objects prev-component)}))
|
||||
:id id
|
||||
:name (:name prev-component)
|
||||
:path (:path prev-component)
|
||||
:main-instance-id (:main-instance-id prev-component)
|
||||
:main-instance-page (:main-instance-page prev-component)
|
||||
:annotation (:annotation prev-component)
|
||||
:objects (:objects prev-component)}))
|
||||
changes)))
|
||||
|
||||
(defn delete-component
|
||||
|
|
|
@ -166,6 +166,7 @@
|
|||
:component-id
|
||||
:component-file
|
||||
:component-root
|
||||
:main-instance
|
||||
:remote-synced
|
||||
:shape-ref
|
||||
:touched))
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
(wrap-object-fn)))))))
|
||||
|
||||
(defn mod-component
|
||||
[file-data {:keys [id name path objects annotation]}]
|
||||
[file-data {:keys [id name path main-instance-id main-instance-page objects annotation]}]
|
||||
(let [wrap-objects-fn feat/*wrap-with-objects-map-fn*]
|
||||
(d/update-in-when file-data [:components id]
|
||||
(fn [component]
|
||||
|
@ -59,6 +59,12 @@
|
|||
(some? path)
|
||||
(assoc :path path)
|
||||
|
||||
(some? main-instance-id)
|
||||
(assoc :main-instance-id main-instance-id)
|
||||
|
||||
(some? main-instance-page)
|
||||
(assoc :main-instance-page main-instance-page)
|
||||
|
||||
(some? objects)
|
||||
(assoc :objects objects)
|
||||
|
||||
|
|
|
@ -164,6 +164,8 @@
|
|||
false
|
||||
(ctk/main-instance? shape)
|
||||
true
|
||||
(ctk/instance-head? shape)
|
||||
false
|
||||
:else
|
||||
(component-main? objects (get objects (:parent-id shape)))))
|
||||
|
||||
|
|
|
@ -867,298 +867,6 @@
|
|||
(println)
|
||||
(dump-page page file libraries* (assoc flags :root-id (:id root))))))))))))))))
|
||||
|
||||
;; Validation
|
||||
|
||||
(declare validate-shape)
|
||||
|
||||
(defn validate-parent-children
|
||||
"Validate parent and children exists, and the link is bidirectional."
|
||||
[shape file page report-error]
|
||||
(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)
|
||||
(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)))
|
||||
|
||||
(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)))))))
|
||||
|
||||
(defn validate-frame
|
||||
"Validate that the frame-id shape exists and is indeed a frame."
|
||||
[shape file page report-error]
|
||||
(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)
|
||||
(when (not= (:type frame) :frame)
|
||||
(report-error :invalid-frame
|
||||
(str/format "Frame %s is not actually a frame" (:frame-id shape))
|
||||
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."
|
||||
[shape file page libraries report-error]
|
||||
(when (nil? (:main-instance shape))
|
||||
(report-error :component-not-main
|
||||
(str/format "Shape expected to be main instance")
|
||||
shape file page))
|
||||
(let [component (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-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))
|
||||
(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))))))
|
||||
|
||||
(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 report-error]
|
||||
(when (some? (:main-instance shape))
|
||||
(report-error :component-not-main
|
||||
(str/format "Shape not expected to be main instance")
|
||||
shape file page))
|
||||
(let [component (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))))))
|
||||
|
||||
(defn validate-component-not-main-not-head
|
||||
"Validate that this shape is not main instance and not head."
|
||||
[shape file page report-error]
|
||||
(when (some? (:main-instance shape))
|
||||
(report-error :component-main
|
||||
(str/format "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)))
|
||||
|
||||
(defn validate-component-root
|
||||
"Validate that this shape is an instance root."
|
||||
[shape file page report-error]
|
||||
(when (nil? (:component-root shape))
|
||||
(report-error :missing-component-root
|
||||
(str/format "Shape should be component root")
|
||||
shape file page)))
|
||||
|
||||
(defn validate-component-not-root
|
||||
"Validate that this shape is not an instance root."
|
||||
[shape file page report-error]
|
||||
(when (some? (:component-root shape))
|
||||
(report-error :missing-component-root
|
||||
(str/format "Shape should not be component root")
|
||||
shape file page)))
|
||||
|
||||
(defn validate-component-ref
|
||||
"Validate that the referenced shape exists in the near component."
|
||||
[shape file page libraries report-error]
|
||||
(let [ref-shape (find-ref-shape file page libraries shape)]
|
||||
(when (nil? ref-shape)
|
||||
(report-error :missing-component-root
|
||||
(str/format "Referenced shape %s not found in near component" (:shape-ref shape))
|
||||
shape file page))))
|
||||
|
||||
(defn validate-component-not-ref
|
||||
"Validate that this shape does not reference other one."
|
||||
[shape file page report-error]
|
||||
(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)))
|
||||
|
||||
(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 report-error]
|
||||
(validate-component-main-head shape file page libraries report-error)
|
||||
(validate-component-root shape file page report-error)
|
||||
(validate-component-not-ref shape file page report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-top :report-error report-error)))
|
||||
|
||||
(defn validate-shape-main-root-nested
|
||||
"Root shape of a nested main instance
|
||||
:main-instance
|
||||
:component-id
|
||||
:component-file"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-main-head shape file page libraries report-error)
|
||||
(validate-component-not-root shape file page report-error)
|
||||
(validate-component-not-ref shape file page report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-nested :report-error report-error)))
|
||||
|
||||
(defn validate-shape-copy-root-top
|
||||
"Root shape of a top copy instance
|
||||
:component-id
|
||||
:component-file
|
||||
:component-root
|
||||
:shape-ref"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-not-main-head shape file page libraries report-error)
|
||||
(validate-component-root shape file page report-error)
|
||||
(validate-component-ref shape file page libraries report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-top :report-error report-error)))
|
||||
|
||||
(defn validate-shape-copy-root-nested
|
||||
"Root shape of a nested copy instance
|
||||
:component-id
|
||||
:component-file
|
||||
:shape-ref"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-not-main-head shape file page libraries report-error)
|
||||
(validate-component-not-root shape file page report-error)
|
||||
(validate-component-ref shape file page libraries report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-nested :report-error report-error)))
|
||||
|
||||
(defn validate-shape-main-not-root
|
||||
"Not-root shape of a main instance
|
||||
(not any attribute)"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-not-main-not-head shape file page report-error)
|
||||
(validate-component-not-root shape file page report-error)
|
||||
(validate-component-not-ref shape file page report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :main-any :report-error report-error)))
|
||||
|
||||
(defn validate-shape-copy-not-root
|
||||
"Not-root shape of a copy instance
|
||||
:shape-ref"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-not-main-not-head shape file page report-error)
|
||||
(validate-component-not-root shape file page report-error)
|
||||
(validate-component-ref shape file page libraries report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :copy-any :report-error report-error)))
|
||||
|
||||
(defn validate-shape-not-component
|
||||
"Shape is not in a component or is a fostered children
|
||||
(not any attribute)"
|
||||
[shape file page libraries report-error]
|
||||
(validate-component-not-main-not-head shape file page report-error)
|
||||
(validate-component-not-root shape file page report-error)
|
||||
(validate-component-not-ref shape file page report-error)
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(validate-shape child-id file page libraries :context :not-component :report-error report-error)))
|
||||
|
||||
(defn validate-shape
|
||||
"Validate referential integrity and semantic coherence of a shape and all its children.
|
||||
|
||||
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? report-error]
|
||||
:or {context :not-component throw? false}}]
|
||||
(let [shape (ctst/get-shape page shape-id)
|
||||
errors (volatile! [])
|
||||
|
||||
report-error (or report-error
|
||||
(fn [code msg shape file page]
|
||||
(if throw?
|
||||
(throw (ex-info msg {:type :validation
|
||||
:code code
|
||||
:hint msg
|
||||
::explain (str/format "file %s\npage %s\nshape %s"
|
||||
(:id file)
|
||||
(:id page)
|
||||
(:id shape))}))
|
||||
(vswap! errors conj {:hint msg
|
||||
:code code
|
||||
:shape shape}))))]
|
||||
|
||||
(dm/assert! (str/format "Shape %s not found" shape-id) (some? shape))
|
||||
|
||||
(validate-parent-children shape file page report-error)
|
||||
(validate-frame shape file page report-error)
|
||||
|
||||
(if (ctk/main-instance? shape)
|
||||
|
||||
(if (ctk/instance-root? 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))
|
||||
|
||||
(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)))
|
||||
|
||||
(if (ctk/instance-head? shape)
|
||||
|
||||
(if (ctk/instance-root? shape)
|
||||
(if (not= context :not-component)
|
||||
(report-error :root-copy-not-allowed
|
||||
(str/format "Root copy not allowed inside other component")
|
||||
shape file page)
|
||||
(validate-shape-copy-root-top shape file page libraries report-error))
|
||||
|
||||
(if (= context :not-component)
|
||||
(report-error :nested-copy-not-allowed
|
||||
(str/format "Nested copy only allowed inside other component")
|
||||
shape file page)
|
||||
(validate-shape-copy-root-nested shape file page libraries report-error)))
|
||||
|
||||
(if (ctn/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))
|
||||
|
||||
(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))
|
||||
|
||||
(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))))))
|
||||
|
||||
@errors))
|
||||
|
||||
;; Export
|
||||
|
||||
(defn- get-component-ref-file
|
||||
|
|
|
@ -271,6 +271,3 @@
|
|||
(update-in [:workspace-libraries file-id :data] cp/process-changes changes)))
|
||||
|
||||
state))))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.repair :as cfr]
|
||||
[app.common.files.validate :as cfv]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.transit :as t]
|
||||
|
@ -21,6 +23,7 @@
|
|||
[app.main.data.workspace.changes :as dwc]
|
||||
[app.main.data.workspace.path.shortcuts]
|
||||
[app.main.data.workspace.shortcuts]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -33,6 +36,8 @@
|
|||
[potok.core :as ptk]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(l/set-level! :debug)
|
||||
|
||||
(defn ^:export set-logging
|
||||
([level]
|
||||
(l/set-level! :app (keyword level)))
|
||||
|
@ -431,14 +436,49 @@
|
|||
([shape-id]
|
||||
(let [file (assoc (get @st/state :workspace-file)
|
||||
:data (get @st/state :workspace-data))
|
||||
page (dm/get-in file [:data :pages-index (get @st/state :current-page-id)])
|
||||
libraries (get @st/state :workspace-libraries)
|
||||
|
||||
errors (ctf/validate-shape (or shape-id uuid/zero)
|
||||
file
|
||||
page
|
||||
libraries)]
|
||||
(clj->js errors))))
|
||||
errors (if shape-id
|
||||
(let [page (dm/get-in file [:data :pages-index (get @st/state :current-page-id)])]
|
||||
(cfv/validate-shape (uuid shape-id) file page libraries))
|
||||
(cfv/validate-file file libraries))]
|
||||
|
||||
(clj->js (d/group-by :code errors)))))
|
||||
|
||||
;; --- Repair file
|
||||
|
||||
(defn ^:export repair
|
||||
[]
|
||||
(let [file (assoc (get @st/state :workspace-file)
|
||||
:data (get @st/state :workspace-data))
|
||||
libraries (get @st/state :workspace-libraries)
|
||||
errors (cfv/validate-file file libraries)]
|
||||
|
||||
(l/dbg :hint "repair current file" :errors (count errors))
|
||||
|
||||
(st/emit!
|
||||
(ptk/reify ::repair-current-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (cond-> #{}
|
||||
(features/active-feature? state :components-v2)
|
||||
(conj "components/v2"))
|
||||
sid (:session-id state)
|
||||
file (get state :workspace-file)
|
||||
file-data (get state :workspace-data)
|
||||
libraries (get state :workspace-libraries)
|
||||
|
||||
changes (-> (cfr/repair-file file-data libraries errors)
|
||||
(get :redo-changes))
|
||||
|
||||
params {:id (:id file)
|
||||
:revn (:revn file)
|
||||
:session-id sid
|
||||
:changes changes
|
||||
:features features}]
|
||||
|
||||
(->> (rp/cmd! :update-file params)
|
||||
(rx/tap #(dom/reload-current-window)))))))))
|
||||
|
||||
(defn ^:export fix-orphan-shapes
|
||||
[]
|
||||
|
|
Loading…
Add table
Reference in a new issue