diff --git a/src/pluto/js.cljs b/src/pluto/js.cljs index 40cc226..3d4fca8 100644 --- a/src/pluto/js.cljs +++ b/src/pluto/js.cljs @@ -12,17 +12,35 @@ (defn component []) (def ctx - {:capacities {:components {'text {:value component} 'view {:value component} 'token-selector {:value component} 'asset-selector {:value component} + {:capacities {:components {'button {:value component :properties {:on-click :event}} + 'text-input {:value component} + 'text {:value component} 'view {:value component} 'token-selector {:value component} 'asset-selector {:value component} 'transaction-status {:value component :properties {:outgoing :string :tx-hash :string}} 'nft-token-viewer {:value component :properties {:token :string}}} - :queries {'get-collectible-token {:value :get-collectible-token}} - :hooks {:commands {:properties {:scope #{:personal-chats :public-chats} + :queries {'get-collectible-token {:value :get-collectible-token} + 'store/get {:value :store/get}} + :events {'alert + {:permissions [:read] + :value :alert} + 'log + {:permissions [:read] + :value :log} + 'store/put + {:permissions [:read] + :value :store/put} + 'http/get + {:permissions [:read] + :value :http/get}} + :hooks {:commands {:properties {:description? :string + :scope #{:personal-chats :public-chats} :short-preview :view :preview :view :parameters [{:id :keyword :type {:one-of #{:text :phone :password :number}} :placeholder :string - :suggestions? :view}]}}}}}) + :suggestions? :view}] + :on-send? :event + :on-receive? :event}}}}}) (defn ^:export parse [m] (reader/parse ctx (:data m))) diff --git a/src/pluto/reader/blocks.cljc b/src/pluto/reader/blocks.cljc index f11a0da..49b3489 100644 --- a/src/pluto/reader/blocks.cljc +++ b/src/pluto/reader/blocks.cljc @@ -13,7 +13,7 @@ (defn resolve-binding-value [v] (cond - (fn? v) @(v) ;; TODO better abstract query + (vector? v) @(re-frame/subscribe v) ;; TODO better abstract query (not (list? v)) v)) (defn resolve-binding-key [k v] diff --git a/src/pluto/reader/errors.cljc b/src/pluto/reader/errors.cljc index c4eec40..580a24f 100644 --- a/src/pluto/reader/errors.cljc +++ b/src/pluto/reader/errors.cljc @@ -25,6 +25,7 @@ ::unknown-component-property ::unknown-query ::unknown-event + ::invalid-component-property-type ::invalid-view ::invalid-property-map ::invalid-block diff --git a/src/pluto/reader/reference.cljc b/src/pluto/reader/reference.cljc index 62fd3e2..cf6e596 100644 --- a/src/pluto/reader/reference.cljc +++ b/src/pluto/reader/reference.cljc @@ -29,17 +29,23 @@ (or (get ext (symbol ns (name s))) (get-in ctx [:capacities (get type->capacity type) s :value]))) +(defn valid-reference? [[name arguments :as value]] + (and (symbol? name) + (>= 2 (count value)) + (or (nil? arguments) (map? arguments) (symbol? arguments)))) + (defn resolve "Resolve a reference defined by a hook ```clojure - (= {:data \"view\"} (resolve {'views/id \"view\"} :view ['id])) + (= {:data \"view\"} (resolve {} {'views/id \"view\"} :view ['id])) ```" [ctx ext type value] - (if-let [s (reference->symbol value)] - (if-let [ns (get type->ns type)] - (if-let [o (resolve-symbol ctx ext type ns s)] - {:data o} - {:errors [(errors/error ::errors/unknown-reference {:value s})]}) - {:errors [(errors/error ::errors/unknown-reference-type {:value type})]}) - {:errors [(errors/error ::errors/invalid-reference {:value value})]})) + (if (valid-reference? value) + (let [s (reference->symbol value)] + (if-let [ns (get type->ns type)] + (if-let [o (resolve-symbol ctx ext type ns s)] + {:data o} + {:errors [(errors/error ::errors/unknown-reference {:value s :type type})]}) + {:errors [(errors/error ::errors/unknown-reference-type {:value type})]})) + {:errors [(errors/error ::errors/invalid-reference {:type type :value value})]})) diff --git a/src/pluto/reader/types.cljc b/src/pluto/reader/types.cljc index 26ebcb1..0daa3cf 100644 --- a/src/pluto/reader/types.cljc +++ b/src/pluto/reader/types.cljc @@ -8,18 +8,26 @@ [pluto.reader.errors :as errors] [pluto.reader.reference :as reference])) +(def reference-types #{:view :event :query}) + (defmulti resolve "Resolve a value based on a type. Returns a map of either: * data with the resolved data * errors encapsulating all errors generated during resolution" (fn [ctx ext type value] - (cond - (keyword? type) type - (:one-of type) :one-of - (set? type) :subset - (map? type) :assoc - (vector? type) :sequence))) + (if (symbol? value) + :symbol + (cond + (keyword? type) type + (:one-of type) :one-of + (set? type) :subset + (map? type) :assoc + (vector? type) :sequence)))) + +(defmethod resolve :symbol [_ _ _ value] + ;; TODO properly validate symbols based on inferred type + {:data value}) (defn invalid-type-value [type value] (errors/error ::errors/invalid-type-value {:type type :value value})) @@ -73,21 +81,26 @@ {} type) {:errors [(errors/error ::errors/invalid-assoc-type {:type type :value value})]})) -(defn- resolve-reference [ctx ext type [name _ :as value] f error] +(defn- resolve-arguments [ctx ext key data arguments] + (resolve ctx ext (get-in ctx [:capacities key data :arguments]) arguments)) + +(defn- reference-with-arguments [ctx ext ref key name arguments] + (if arguments + (let [{:keys [data errors]} (resolve-arguments ctx ext key name arguments)] + (errors/merge-errors {:data [ref data]} errors)) + {:data [ref]})) + +(defn resolve-reference [ctx ext type [name arguments :as value] key error] (let [{:keys [data errors]} (reference/resolve ctx ext type value)] - (merge (when data {:data (f data)}) - (when errors - {:errors (apply conj [(errors/error error name)] errors)})))) + (merge (when data (reference-with-arguments ctx ext data key name arguments)) + (when errors + {:errors (apply conj [(errors/error error name)] errors)})))) -(defmethod resolve :event [ctx ext type [_ properties :as value]] - (resolve-reference ctx ext type value - (fn [data] #(re-frame/dispatch (if properties [data properties] [data]))) - ::errors/unknown-event)) +(defmethod resolve :event [ctx ext type [name arguments :as value]] + (resolve-reference ctx ext type value :events ::errors/unknown-event)) -(defmethod resolve :query [ctx ext type [name properties :as value]] - (resolve-reference ctx ext type value - (fn [data] #(re-frame/subscribe (if properties [data properties] [data]))) - ::errors/unknown-query)) +(defmethod resolve :query [ctx ext type [name arguments :as value]] + (resolve-reference ctx ext type value :queries ::errors/unknown-query)) (defmethod resolve :default [_ _ type value] {:errors [(errors/error ::errors/invalid-type (merge {:type type} (when value {:value value})))]}) diff --git a/src/pluto/reader/views.cljc b/src/pluto/reader/views.cljc index bd4a00b..0cc9066 100644 --- a/src/pluto/reader/views.cljc +++ b/src/pluto/reader/views.cljc @@ -1,5 +1,6 @@ (ns pluto.reader.views (:require [clojure.spec.alpha :as spec] + [re-frame.core :as re-frame] [pluto.reader.blocks :as blocks] [pluto.reader.destructuring :as destructuring] [pluto.reader.errors :as errors] @@ -43,7 +44,9 @@ (fn? o) o (symbol? o) (get-in ctx [:capacities :components o :value]))) -(defmulti resolve-default-component-properties (fn [property value] property)) +(defmulti resolve-default-component-properties + "Resolve default properties available for all components." + (fn [property value] property)) (defmethod resolve-default-component-properties :style [_ value] {:data value}) @@ -51,12 +54,23 @@ (defmethod resolve-default-component-properties :default [_ value] nil) +(defn resolve-existing-component-property-type [ctx ext type v] + (let [{:keys [data errors] :as t} (types/resolve ctx ext type v)] + (if (= :event type) + (errors/merge-errors {:data #(re-frame/dispatch data)} errors) + t))) + +(defn resolve-custom-component-properties [ctx ext component k v] + (if-let [type (get-in ctx [:capacities :components component :properties k])] + (if-not (and (types/reference-types type) (not= :event type)) + ;; TODO Infer symbol types and fail if type does not match + (if (symbol? v) v (resolve-existing-component-property-type ctx ext type v)) + {:errors [(errors/error ::errors/invalid-component-property-type {:component component :property k :type type})]}) + {:errors [(errors/error ::errors/unknown-component-property {:component component :property k})]})) + (defn- resolve-component-property [ctx ext component k v] (or (resolve-default-component-properties k v) - (if-let [type (get-in ctx [:capacities :components component :properties k])] - ;; TODO Infer symbol types and fail if type does not match - (if (symbol? v) v (types/resolve ctx ext type v)) - {:errors [(errors/error ::errors/unknown-component-property {:component component :property k})]}))) + (resolve-custom-component-properties ctx ext component k v))) (defn- resolve-property [ctx ext component k v] (if (component? component) diff --git a/test/pluto/reader/block_test.cljc b/test/pluto/reader/block_test.cljc index ed5acc2..46a932f 100644 --- a/test/pluto/reader/block_test.cljc +++ b/test/pluto/reader/block_test.cljc @@ -21,7 +21,7 @@ (is (= {:data [blocks/let-block {:env '{a {:b 1} b 1}} 'b]} (blocks/parse {} {} '(let [{a :a} {:a {:b 1}} {b :b} a] b)))) (is (empty? - (:errors (blocks/parse {:capacities {:queries {'aa {:value :a}}}} {} '(let [x 1 {a :a} [aa {:x x}]] a))))) + (:errors (blocks/parse {:capacities {:queries {'aa {:value :a :arguments {:x :string}}}}} {} '(let [x 1 {a :a} [aa {:x x}]] a))))) (is (= {:data [blocks/let-block {:env {'s "Hello"}} ['test {} 's]]} (blocks/parse {} {} (list 'let ['s "Hello"] ['test {} 's])))) diff --git a/test/pluto/reader/reference_test.cljc b/test/pluto/reader/reference_test.cljc index 096ed75..3d3a1e9 100644 --- a/test/pluto/reader/reference_test.cljc +++ b/test/pluto/reader/reference_test.cljc @@ -17,12 +17,22 @@ (is (= 'test (reference/reference->symbol ['test]))) (is (= 'views/id (reference/reference->symbol ['views/id {}])))) +(deftest valid-reference? + (is (false? (reference/valid-reference? ""))) + (is (true? (reference/valid-reference? ['test]))) + (is (true? (reference/valid-reference? ['views/id {}]))) + (is (false? (reference/valid-reference? ['views/id {} {}]))) + (is (false? (reference/valid-reference? ['views/id 1 {}]))) + (is (false? (reference/valid-reference? ["id" {}]))) + (is (true? (reference/valid-reference? ['views/id {}]))) + (is (true? (reference/valid-reference? ['views/id 'arg])))) + (deftest resolve (is (= {:errors [{::errors/type ::errors/unknown-reference - ::errors/value {:value 'id}}]} + ::errors/value {:value 'id :type :view}}]} (reference/resolve {} {} :view ['id]))) (is (= {:errors [{::errors/type ::errors/invalid-reference - ::errors/value {:value ""}}]} + ::errors/value {:value "" :type :view}}]} (reference/resolve {} {'views/id "view"} :view ""))) (is (= {:errors [{::errors/type ::errors/unknown-reference-type ::errors/value {:value :unknown}}]} diff --git a/test/pluto/reader/types_test.cljc b/test/pluto/reader/types_test.cljc index 4ce7060..030967d 100644 --- a/test/pluto/reader/types_test.cljc +++ b/test/pluto/reader/types_test.cljc @@ -106,8 +106,12 @@ (is (= {:errors [{::errors/type ::errors/unknown-event ::errors/value 'event} {::errors/type ::errors/unknown-reference - ::errors/value {:value 'event}}]} + ::errors/value {:value 'event :type :event}}]} (types/resolve {} {} :event ['event]))) (let [{:keys [data errors]} (types/resolve {:capacities {:events {'event {:value :event}}}} {} :event ['event])] (is (not errors)) - (is data))) + (is data)) + (let [{:keys [data errors]} (types/resolve {:capacities {:events {'event {:value :event :arguments {:on-finished? :event}}}}} + {} :event ['event {:on-finished ['event]}])] + (is (not errors)) + (is (= [:event {:on-finished [:event]}] data)))) diff --git a/test/pluto/reader/views_test.cljc b/test/pluto/reader/views_test.cljc index c471ca4..2c4b094 100644 --- a/test/pluto/reader/views_test.cljc +++ b/test/pluto/reader/views_test.cljc @@ -52,7 +52,7 @@ (deftest resolve (is (= [:text "Hello"] ((:data (types/resolve {:capacities {:components {'text {:value :text}}}} {'views/main ['text "Hello"]} :view ['views/main])) {}))) (is (= {:errors [{::errors/type ::errors/unknown-reference, - ::errors/value {:value 'views/unknown}}]} + ::errors/value {:value 'views/unknown :type :view}}]} (types/resolve {:capacities {:components {'text {:value :text}}}} {'views/main ['text "Hello"]} :view ['views/unknown])))) (deftest invalid-view-element-spec-errors