Validate queries/events arguments

This commit is contained in:
Julien Eluard 2018-10-14 15:26:39 +02:00
parent 214adccbdb
commit 4de10d1d4b
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
10 changed files with 108 additions and 42 deletions

View File

@ -12,17 +12,35 @@
(defn component []) (defn component [])
(def ctx (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}} 'transaction-status {:value component :properties {:outgoing :string :tx-hash :string}}
'nft-token-viewer {:value component :properties {:token :string}}} 'nft-token-viewer {:value component :properties {:token :string}}}
:queries {'get-collectible-token {:value :get-collectible-token}} :queries {'get-collectible-token {:value :get-collectible-token}
:hooks {:commands {:properties {:scope #{:personal-chats :public-chats} '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 :short-preview :view
:preview :view :preview :view
:parameters [{:id :keyword :parameters [{:id :keyword
:type {:one-of #{:text :phone :password :number}} :type {:one-of #{:text :phone :password :number}}
:placeholder :string :placeholder :string
:suggestions? :view}]}}}}}) :suggestions? :view}]
:on-send? :event
:on-receive? :event}}}}})
(defn ^:export parse [m] (defn ^:export parse [m]
(reader/parse ctx (:data m))) (reader/parse ctx (:data m)))

View File

@ -13,7 +13,7 @@
(defn resolve-binding-value [v] (defn resolve-binding-value [v]
(cond (cond
(fn? v) @(v) ;; TODO better abstract query (vector? v) @(re-frame/subscribe v) ;; TODO better abstract query
(not (list? v)) v)) (not (list? v)) v))
(defn resolve-binding-key [k v] (defn resolve-binding-key [k v]

View File

@ -25,6 +25,7 @@
::unknown-component-property ::unknown-component-property
::unknown-query ::unknown-query
::unknown-event ::unknown-event
::invalid-component-property-type
::invalid-view ::invalid-view
::invalid-property-map ::invalid-property-map
::invalid-block ::invalid-block

View File

@ -29,17 +29,23 @@
(or (get ext (symbol ns (name s))) (or (get ext (symbol ns (name s)))
(get-in ctx [:capacities (get type->capacity type) s :value]))) (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 (defn resolve
"Resolve a reference defined by a hook "Resolve a reference defined by a hook
```clojure ```clojure
(= {:data \"view\"} (resolve {'views/id \"view\"} :view ['id])) (= {:data \"view\"} (resolve {} {'views/id \"view\"} :view ['id]))
```" ```"
[ctx ext type value] [ctx ext type value]
(if-let [s (reference->symbol value)] (if (valid-reference? value)
(if-let [ns (get type->ns type)] (let [s (reference->symbol value)]
(if-let [o (resolve-symbol ctx ext type ns s)] (if-let [ns (get type->ns type)]
{:data o} (if-let [o (resolve-symbol ctx ext type ns s)]
{:errors [(errors/error ::errors/unknown-reference {:value s})]}) {:data o}
{:errors [(errors/error ::errors/unknown-reference-type {:value type})]}) {:errors [(errors/error ::errors/unknown-reference {:value s :type type})]})
{:errors [(errors/error ::errors/invalid-reference {:value value})]})) {:errors [(errors/error ::errors/unknown-reference-type {:value type})]}))
{:errors [(errors/error ::errors/invalid-reference {:type type :value value})]}))

View File

@ -8,18 +8,26 @@
[pluto.reader.errors :as errors] [pluto.reader.errors :as errors]
[pluto.reader.reference :as reference])) [pluto.reader.reference :as reference]))
(def reference-types #{:view :event :query})
(defmulti resolve (defmulti resolve
"Resolve a value based on a type. "Resolve a value based on a type.
Returns a map of either: Returns a map of either:
* data with the resolved data * data with the resolved data
* errors encapsulating all errors generated during resolution" * errors encapsulating all errors generated during resolution"
(fn [ctx ext type value] (fn [ctx ext type value]
(cond (if (symbol? value)
(keyword? type) type :symbol
(:one-of type) :one-of (cond
(set? type) :subset (keyword? type) type
(map? type) :assoc (:one-of type) :one-of
(vector? type) :sequence))) (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] (defn invalid-type-value [type value]
(errors/error ::errors/invalid-type-value {:type type :value value})) (errors/error ::errors/invalid-type-value {:type type :value value}))
@ -73,21 +81,26 @@
{} type) {} type)
{:errors [(errors/error ::errors/invalid-assoc-type {:type type :value value})]})) {: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)] (let [{:keys [data errors]} (reference/resolve ctx ext type value)]
(merge (when data {:data (f data)}) (merge (when data (reference-with-arguments ctx ext data key name arguments))
(when errors (when errors
{:errors (apply conj [(errors/error error name)] errors)})))) {:errors (apply conj [(errors/error error name)] errors)}))))
(defmethod resolve :event [ctx ext type [_ properties :as value]] (defmethod resolve :event [ctx ext type [name arguments :as value]]
(resolve-reference ctx ext type value (resolve-reference ctx ext type value :events ::errors/unknown-event))
(fn [data] #(re-frame/dispatch (if properties [data properties] [data])))
::errors/unknown-event))
(defmethod resolve :query [ctx ext type [name properties :as value]] (defmethod resolve :query [ctx ext type [name arguments :as value]]
(resolve-reference ctx ext type value (resolve-reference ctx ext type value :queries ::errors/unknown-query))
(fn [data] #(re-frame/subscribe (if properties [data properties] [data])))
::errors/unknown-query))
(defmethod resolve :default [_ _ type value] (defmethod resolve :default [_ _ type value]
{:errors [(errors/error ::errors/invalid-type (merge {:type type} (when value {:value value})))]}) {:errors [(errors/error ::errors/invalid-type (merge {:type type} (when value {:value value})))]})

View File

@ -1,5 +1,6 @@
(ns pluto.reader.views (ns pluto.reader.views
(:require [clojure.spec.alpha :as spec] (:require [clojure.spec.alpha :as spec]
[re-frame.core :as re-frame]
[pluto.reader.blocks :as blocks] [pluto.reader.blocks :as blocks]
[pluto.reader.destructuring :as destructuring] [pluto.reader.destructuring :as destructuring]
[pluto.reader.errors :as errors] [pluto.reader.errors :as errors]
@ -43,7 +44,9 @@
(fn? o) o (fn? o) o
(symbol? o) (get-in ctx [:capacities :components o :value]))) (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] (defmethod resolve-default-component-properties :style [_ value]
{:data value}) {:data value})
@ -51,12 +54,23 @@
(defmethod resolve-default-component-properties :default [_ value] (defmethod resolve-default-component-properties :default [_ value]
nil) 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] (defn- resolve-component-property [ctx ext component k v]
(or (resolve-default-component-properties k v) (or (resolve-default-component-properties k v)
(if-let [type (get-in ctx [:capacities :components component :properties k])] (resolve-custom-component-properties ctx ext component k v)))
;; 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})]})))
(defn- resolve-property [ctx ext component k v] (defn- resolve-property [ctx ext component k v]
(if (component? component) (if (component? component)

View File

@ -21,7 +21,7 @@
(is (= {:data [blocks/let-block {:env '{a {:b 1} b 1}} 'b]} (is (= {:data [blocks/let-block {:env '{a {:b 1} b 1}} 'b]}
(blocks/parse {} {} '(let [{a :a} {:a {:b 1}} {b :b} a] b)))) (blocks/parse {} {} '(let [{a :a} {:a {:b 1}} {b :b} a] b))))
(is (empty? (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]]} (is (= {:data [blocks/let-block {:env {'s "Hello"}} ['test {} 's]]}
(blocks/parse {} {} (list 'let ['s "Hello"] ['test {} 's])))) (blocks/parse {} {} (list 'let ['s "Hello"] ['test {} 's]))))

View File

@ -17,12 +17,22 @@
(is (= 'test (reference/reference->symbol ['test]))) (is (= 'test (reference/reference->symbol ['test])))
(is (= 'views/id (reference/reference->symbol ['views/id {}])))) (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 (deftest resolve
(is (= {:errors [{::errors/type ::errors/unknown-reference (is (= {:errors [{::errors/type ::errors/unknown-reference
::errors/value {:value 'id}}]} ::errors/value {:value 'id :type :view}}]}
(reference/resolve {} {} :view ['id]))) (reference/resolve {} {} :view ['id])))
(is (= {:errors [{::errors/type ::errors/invalid-reference (is (= {:errors [{::errors/type ::errors/invalid-reference
::errors/value {:value ""}}]} ::errors/value {:value "" :type :view}}]}
(reference/resolve {} {'views/id "view"} :view ""))) (reference/resolve {} {'views/id "view"} :view "")))
(is (= {:errors [{::errors/type ::errors/unknown-reference-type (is (= {:errors [{::errors/type ::errors/unknown-reference-type
::errors/value {:value :unknown}}]} ::errors/value {:value :unknown}}]}

View File

@ -106,8 +106,12 @@
(is (= {:errors [{::errors/type ::errors/unknown-event (is (= {:errors [{::errors/type ::errors/unknown-event
::errors/value 'event} ::errors/value 'event}
{::errors/type ::errors/unknown-reference {::errors/type ::errors/unknown-reference
::errors/value {:value 'event}}]} ::errors/value {:value 'event :type :event}}]}
(types/resolve {} {} :event ['event]))) (types/resolve {} {} :event ['event])))
(let [{:keys [data errors]} (types/resolve {:capacities {:events {'event {:value :event}}}} {} :event ['event])] (let [{:keys [data errors]} (types/resolve {:capacities {:events {'event {:value :event}}}} {} :event ['event])]
(is (not errors)) (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))))

View File

@ -52,7 +52,7 @@
(deftest resolve (deftest resolve
(is (= [:text "Hello"] ((:data (types/resolve {:capacities {:components {'text {:value :text}}}} {'views/main ['text "Hello"]} :view ['views/main])) {}))) (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, (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])))) (types/resolve {:capacities {:components {'text {:value :text}}}} {'views/main ['text "Hello"]} :view ['views/unknown]))))
(deftest invalid-view-element-spec-errors (deftest invalid-view-element-spec-errors