Improve dispatcher impl usability.

This commit is contained in:
Andrey Antukh 2019-11-29 12:52:29 +01:00
parent 761a3c102f
commit 45cc4e0d27

View file

@ -16,9 +16,9 @@
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
[uxbox.util.exceptions :as ex]) [uxbox.util.exceptions :as ex])
(:import (:import
clojure.lang.IDeref
clojure.lang.MapEntry
java.util.Map java.util.Map
java.util.List
java.util.Map$Entry
java.util.HashMap)) java.util.HashMap))
(definterface IDispatcher (definterface IDispatcher
@ -27,9 +27,15 @@
(deftype Dispatcher [reg attr interceptors] (deftype Dispatcher [reg attr interceptors]
IDispatcher IDispatcher
(add [this key f metadata] (add [this key f metadata]
(.put ^Map reg key (Map/entry f metadata)) (.put ^Map reg key (MapEntry/create f metadata))
nil) nil)
clojure.lang.IDeref
(deref [_]
{:registry reg
:attr attr
:interceptors interceptors})
clojure.lang.IFn clojure.lang.IFn
(invoke [_ params] (invoke [_ params]
(let [key (get params attr) (let [key (get params attr)
@ -38,8 +44,8 @@
(p/rejected (ex/error :type :not-found (p/rejected (ex/error :type :not-found
:code :method-not-found :code :method-not-found
:hint "No method found for the current request.")) :hint "No method found for the current request."))
(let [f (.getKey ^Map$Entry entry) (let [f (.key ^MapEntry entry)
m (.getValue ^Map$Entry entry) m (.val ^MapEntry entry)
d (p/deferred)] d (p/deferred)]
(sp/execute (conj interceptors f) (sp/execute (conj interceptors f)
@ -57,25 +63,53 @@
`(defonce ~sname (Dispatcher. (HashMap.) `(defonce ~sname (Dispatcher. (HashMap.)
~dispatch-by ~dispatch-by
~interceptors))) ~interceptors)))
(defn parse-defmethod
[args]
(loop [r {}
s 0
v (first args)
n (rest args)]
(case s
0 (if (symbol? v)
(recur (assoc r :sym v) 1 (first n) (rest n))
(throw (ex-info "first arg to `defmethod` should be a symbol" {})))
1 (if (qualified-keyword? v)
(recur (-> r
(assoc :key (keyword (name v)))
(assoc :meta {:spec v :doc nil}))
3 (first n) (rest n))
(recur r (inc s) v n))
2 (if (simple-keyword? v)
(recur (-> r
(assoc :key v)
(assoc :meta {:doc nil}))
3 (first n) (rest n))
(throw (ex-info "second arg to `defmethod` should be a keyword" {})))
3 (if (string? v)
(recur (update r :meta assoc :doc v) (inc s) (first n) (rest n))
(recur r 4 v n))
4 (if (map? v)
(recur (update r :meta merge v) (inc s) (first n) (rest n))
(recur r 5 v n))
5 (if (vector? v)
(assoc r :args v :body n)
(throw (ex-info "missing arguments vector" {}))))))
(defmacro defmethod (defmacro defmethod
[sname key metadata args & rest] [& args]
(s/assert symbol? sname) (let [{:keys [key meta sym args body]} (parse-defmethod args)
(s/assert keyword? key) f `(fn ~args ~@body)]
(s/assert map? metadata)
(s/assert vector? args)
(let [f `(fn ~args ~@rest)]
`(do `(do
(s/assert dispatcher? ~sname) (s/assert dispatcher? ~sym)
(.add ~sname ~key ~f ~metadata) (.add ~sym ~key ~f ~meta)
~sname))) ~sym)))
(def spec-interceptor (def spec-interceptor
"An interceptor that conforms the request with the user provided "An interceptor that conforms the request with the user provided
spec." spec."
{:enter (fn [{:keys [request] :as data}] {:enter (fn [{:keys [request] :as data}]
(let [{:keys [spec]} (meta request)] (let [{:keys [spec]} (meta request)]
(if spec (if-let [spec (s/get-spec spec)]
(let [result (s/conform spec request)] (let [result (s/conform spec request)]
(if (not= result ::s/invalid) (if (not= result ::s/invalid)
(assoc data :request result) (assoc data :request result)