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.exceptions :as ex])
(:import
clojure.lang.IDeref
clojure.lang.MapEntry
java.util.Map
java.util.List
java.util.Map$Entry
java.util.HashMap))
(definterface IDispatcher
@ -27,9 +27,15 @@
(deftype Dispatcher [reg attr interceptors]
IDispatcher
(add [this key f metadata]
(.put ^Map reg key (Map/entry f metadata))
(.put ^Map reg key (MapEntry/create f metadata))
nil)
clojure.lang.IDeref
(deref [_]
{:registry reg
:attr attr
:interceptors interceptors})
clojure.lang.IFn
(invoke [_ params]
(let [key (get params attr)
@ -38,8 +44,8 @@
(p/rejected (ex/error :type :not-found
:code :method-not-found
:hint "No method found for the current request."))
(let [f (.getKey ^Map$Entry entry)
m (.getValue ^Map$Entry entry)
(let [f (.key ^MapEntry entry)
m (.val ^MapEntry entry)
d (p/deferred)]
(sp/execute (conj interceptors f)
@ -57,25 +63,53 @@
`(defonce ~sname (Dispatcher. (HashMap.)
~dispatch-by
~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
[sname key metadata args & rest]
(s/assert symbol? sname)
(s/assert keyword? key)
(s/assert map? metadata)
(s/assert vector? args)
(let [f `(fn ~args ~@rest)]
[& args]
(let [{:keys [key meta sym args body]} (parse-defmethod args)
f `(fn ~args ~@body)]
`(do
(s/assert dispatcher? ~sname)
(.add ~sname ~key ~f ~metadata)
~sname)))
(s/assert dispatcher? ~sym)
(.add ~sym ~key ~f ~meta)
~sym)))
(def spec-interceptor
"An interceptor that conforms the request with the user provided
spec."
{:enter (fn [{:keys [request] :as data}]
(let [{:keys [spec]} (meta request)]
(if spec
(if-let [spec (s/get-spec spec)]
(let [result (s/conform spec request)]
(if (not= result ::s/invalid)
(assoc data :request result)