Skip to content

Commit 849bac0

Browse files
committed
Add validating lifecycles
1 parent 93132b6 commit 849bac0

File tree

2 files changed

+109
-12
lines changed

2 files changed

+109
-12
lines changed

src/cljfx/dev.clj

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
(ns cljfx.dev
22
(:require [cljfx.lifecycle :as lifecycle]
33
[cljfx.api :as fx]
4-
[clojure.spec.alpha :as s]))
4+
[clojure.spec.alpha :as s]
5+
[clojure.set :as set]))
56

67
(def ^:private registry
78
;; types is a map:
@@ -49,9 +50,9 @@
4950
(defmacro defdynaspec [name & fn-tail]
5051
(let [multi-name (symbol (str name "-multi-fn"))]
5152
`(do
52-
(defmulti ~multi-name (constantly nil))
53-
(defmethod ~multi-name nil ~@fn-tail)
54-
(def ~name (s/multi-spec ~multi-name nil)))))
53+
(defmulti ~multi-name (constantly :cljfx/desc))
54+
(defmethod ~multi-name :cljfx/desc ~@fn-tail)
55+
(def ~name (s/multi-spec ~multi-name :cljfx/desc)))))
5556

5657
(defn- keyword-id-should-be-registered [_] false)
5758
(def ^:private keyword-id-should-be-registered-spec (s/spec keyword-id-should-be-registered))
@@ -202,7 +203,7 @@
202203
{:label "Value type" :fn #(-> % props short-prop-help-string)})))
203204
(when (and (not props) (:spec type))
204205
(println "Spec:")
205-
(println (s/form (:spec type))))
206+
(println (s/describe (:spec type))))
206207
(when (and (not type) (not props))
207208
(println '???)))
208209

@@ -239,12 +240,20 @@
239240
:else
240241
(println '???))))
241242

243+
(load "dev/validation")
244+
245+
(defn wrap-type->lifecycle [type->lifecycle]
246+
(fn [type]
247+
(wrap-lifecycle type (type->lifecycle type))))
248+
249+
(def type->lifecycle
250+
(wrap-type->lifecycle (some-fn fx/keyword->lifecycle fx/fn->lifecycle)))
251+
242252
;; next steps:
243-
;; 1. dev cljfx type->lifecycle wrapper that validates and contextualizes errors
244-
;; in terms of a cljfx component hierarchy
245-
;; 2. documentation
246-
;; 3. release on clojars
253+
;; - profile if validation is a bottleneck?
254+
;; - documentation
255+
;; - release on clojars
247256
;; stretch goals
248-
;; 3. ui reference for searching the props/types/etc
249-
;; 4. dev cljfx type->lifecycle wrapper that adds inspector capabilities.
250-
;; 5. dev ui builder
257+
;; - ui reference for searching the props/types/etc
258+
;; - dev cljfx type->lifecycle wrapper that adds inspector capabilities.
259+
;; - dev ui builder

src/cljfx/dev/validation.clj

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
(in-ns 'cljfx.dev)
2+
3+
(defn- type->string [fx-type]
4+
(if (fn? fx-type)
5+
(-> fx-type class .getName Compiler/demunge)
6+
fx-type))
7+
8+
(defn- re-throw-with-stack [^Throwable ex stack]
9+
(if (::cause (ex-data ex))
10+
(throw ex)
11+
(throw (doto (ex-info
12+
(str (ex-message ex)
13+
"\n\nCljfx component stack:\n "
14+
(->> stack (map type->string) (str/join "\n ")))
15+
{::cause ex})
16+
(.setStackTrace (.getStackTrace ex))))))
17+
18+
(defn- explain-str [explain-data]
19+
(->> explain-data
20+
::s/problems
21+
(mapcat (fn [{:keys [pred val in] :as problem}]
22+
(cond
23+
(and (sequential? pred)
24+
(= `only-keys (first pred)))
25+
(let [ks (set/difference (set (keys val)) (second pred))]
26+
(for [k ks]
27+
(assoc problem :val k :reason "unexpected prop")))
28+
29+
(and (sequential? pred)
30+
(= `keys-satisfy (first pred)))
31+
(if (map? val)
32+
(let [k->spec (second pred)]
33+
(for [[k spec-form] k->spec
34+
:let [v (get val k ::not-found)]
35+
:when (not= v ::not-found)
36+
:let [spec (eval spec-form)]
37+
problem (::s/problems (s/explain-data spec v))]
38+
(update problem :in #(into (conj (or in []) k) %))))
39+
[(assoc problem :reason 'map?)])
40+
41+
(= `valid-fx-type? pred)
42+
(if (contains? val :fx/type)
43+
[(-> problem
44+
(update :val :fx/type)
45+
(update :in conj :fx/type))]
46+
[(assoc problem :reason "(contains? % :fx/type)")])
47+
48+
49+
:else
50+
[problem])))
51+
(map (fn [{:keys [pred val in reason]}]
52+
(str val
53+
" - failed: "
54+
(or reason (let [abbrev (s/abbrev pred)]
55+
(cond-> abbrev (sequential? abbrev) pr-str)))
56+
(when (seq in)
57+
(str " in " in)))))
58+
(str/join "\n")))
59+
60+
(defn- ensure-valid-desc [desc fx-type]
61+
(when-let [explain-data (s/explain-data :cljfx/desc (assoc desc :fx/type fx-type))]
62+
(throw (ex-info (str "Invalid cljfx description of " (type->string fx-type) " type:\n"
63+
(explain-str explain-data))
64+
explain-data))))
65+
66+
(defn- wrap-lifecycle [fx-type lifecycle]
67+
(reify lifecycle/Lifecycle
68+
(create [_ desc opts]
69+
(let [stack (conj (::stack opts) fx-type)
70+
opts (assoc opts ::stack stack)]
71+
(try
72+
(ensure-valid-desc desc fx-type)
73+
(lifecycle/create lifecycle desc opts)
74+
(catch Exception ex (re-throw-with-stack ex stack)))))
75+
(advance [_ component desc opts]
76+
(let [stack (conj (::stack opts) fx-type)
77+
opts (assoc opts ::stack stack)]
78+
(try
79+
(ensure-valid-desc desc fx-type)
80+
(lifecycle/advance lifecycle component desc opts)
81+
(catch Exception ex (re-throw-with-stack ex stack)))))
82+
(delete [_ component opts]
83+
(let [stack (conj (::stack opts) fx-type)
84+
opts (assoc opts ::stack stack)]
85+
(try
86+
(lifecycle/delete lifecycle component opts)
87+
(catch Exception ex (re-throw-with-stack ex stack))))
88+
(lifecycle/delete lifecycle component opts))))

0 commit comments

Comments
 (0)