Skip to content

Commit

Permalink
Middleware (#2)
Browse files Browse the repository at this point in the history
* middleware & dsl

* update version
  • Loading branch information
darkleaf authored Nov 28, 2016
1 parent 2b6625b commit 9ae61b3
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 81 deletions.
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject darkleaf/router "0.2.0-alpha"
(defproject darkleaf/router "0.2.0-alpha1"
:description "Bidirectional Ring router. REST oriented."
:url "https://github.com/darkleaf/router"
:license {:name "Eclipse Public License"
Expand Down
217 changes: 139 additions & 78 deletions src/darkleaf/router.cljc
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
(ns darkleaf.router
(:require [clojure.string :refer [split join]]))

;; ~~~~~~~~~~ Utils ~~~~~~~~~~

(defn- parse-args [ordinal-args-count xs]
"(=
(parse-args 4 [1 2 3 4 :a 5 :b 6 7 8 9])
[1 2 3 4 {:a 5, :b 6} [7 8 9]])"
{:pre (< ordinal-args-count (count xs))}
(let [ordinal-args (take ordinal-args-count xs)
xs (drop ordinal-args-count xs)]
(loop [opts {}
xs xs]
(if (keyword? (first xs))
(do
(assert (some? (second xs)))
(recur (assoc opts (first xs) (second xs))
(drop 2 xs)))
(-> ordinal-args
(vec)
(conj opts)
(conj xs))))))

;; ~~~~~~~~~~ Model ~~~~~~~~~~

(defprotocol Item
Expand All @@ -23,12 +44,18 @@
(defn- composite [& children]
(Composite. children))

(defrecord Scope [id handle-impl fill-impl children]
(defn- add-middleware [req middleware]
(if middleware
(update req ::middlewares conj middleware)
req))

(defrecord Scope [id handle-impl fill-impl middleware children]
Item
(handle [_ req]
(some-> req
(handle-impl)
(update ::scope conj id)
(add-middleware middleware)
(some-handle children)))
(fill [_ req]
(when (= id (peek (::scope req)))
Expand All @@ -37,9 +64,22 @@
(fill-impl)
(some-fill children)))))

(defn- scope [id handle-impl fill-impl children]
(let [children (remove nil? children)]
(Scope. id handle-impl fill-impl children)))
(defn- scope
([id handle-impl fill-impl children]
(scope id handle-impl fill-impl nil children))
([id handle-impl fill-impl middleware children]
(let [children (remove nil? children)]
(Scope. id handle-impl fill-impl middleware children))))

(defn- handle-with-middlewares [req handler]
(if (empty? (::middlewares req))
(-> req
(dissoc ::middlewares)
(handler))
(let [middleware (peek (::middlewares req))
new-req (update req ::middlewares pop)
new-handler (middleware handler)]
(recur new-req new-handler))))

(defrecord Action [id handle-impl fill-impl handler]
Item
Expand All @@ -48,7 +88,7 @@
(handle-impl)
(dissoc ::segments)
(assoc ::action id)
(handler)))
(handle-with-middlewares handler)))
(fill [_ req]
(when (= id (::action req))
(-> req
Expand All @@ -75,18 +115,32 @@
(update ::segments conj segment)))]
(Action. id handle-impl fill-impl handler))))

(defrecord Wrapper [middleware children]
Item
(handle [_ req]
(-> req
(add-middleware middleware)
(some-handle children)))
(fill [_ req]
(some-fill req children)))

(defn wrapper [middleware & children]
(Wrapper. middleware children))

;; ~~~~~~~~~~~ Scopes ~~~~~~~~~~

(defn section [id & children]
{:pre [(keyword? id)
(every? #(or (nil? %) (satisfies? Item %)) children)]}
(let [segment (name id)
handle-impl (fn [req]
(when (= segment (peek (::segments req)))
(update req ::segments pop)))
fill-impl (fn [req]
(update req ::segments conj segment))]
(scope id handle-impl fill-impl children)))
(defn section [& args]
(let [[id
{:keys [middleware]}
children]
(parse-args 1 args)]
(let [segment (name id)
handle-impl (fn [req]
(when (= segment (peek (::segments req)))
(update req ::segments pop)))
fill-impl (fn [req]
(update req ::segments conj segment))]
(scope id handle-impl fill-impl middleware children))))

;; ~~~~~~~~~~ Resources ~~~~~~~~~~

Expand All @@ -102,7 +156,7 @@
identity)]
(scope scope-id handle-impl fill-impl children)))

(defn- resources-member-scope [plural-name singular-name segment & children]
(defn- resources-member-scope [plural-name singular-name segment middleware & children]
(let [id-key (keyword (str (name singular-name) "-id"))
handle-impl (if segment
(fn [req]
Expand Down Expand Up @@ -133,41 +187,44 @@
(fn [req]
(let [id (get-in req [::params id-key])]
(update req ::segments conj id))))]
(scope singular-name handle-impl fill-impl children)))

(defn resources [plural-name singular-name controller
& {:keys [segment nested]
:or {segment (name plural-name)
nested []}}]
(let [index-action (when-let [handler (:index controller)]
(action :index :get handler))
new-action (when-let [handler (:new controller)]
(action :new :get "new" handler))
create-action (when-let [handler (:create controller)]
(action :create :post handler))
show-action (when-let [handler (:show controller)]
(action :show :get handler))
edit-action (when-let [handler (:edit controller)]
(action :edit :get "edit" handler))
update-action (when-let [handler (:update controller)]
(action :update :patch handler))
put-action (when-let [handler (:put controller)]
(action :put :put handler))
destroy-action (when-let [handler (:destroy controller)]
(action :destroy :delete handler))]
(composite
(resources-collection-scope plural-name segment
index-action)
(resources-collection-scope singular-name segment
new-action
create-action)
(apply resources-member-scope plural-name singular-name segment
(into nested
[show-action
edit-action
update-action
put-action
destroy-action])))))
(scope singular-name handle-impl fill-impl middleware children)))

(defn resources [& args]
(let [[plural-name singular-name controller
{:keys [segment nested]
:or {segment (name plural-name)}}
nested]
(parse-args 3 args)]
(let [index-action (when-let [handler (:index controller)]
(action :index :get handler))
new-action (when-let [handler (:new controller)]
(action :new :get "new" handler))
create-action (when-let [handler (:create controller)]
(action :create :post handler))
show-action (when-let [handler (:show controller)]
(action :show :get handler))
edit-action (when-let [handler (:edit controller)]
(action :edit :get "edit" handler))
update-action (when-let [handler (:update controller)]
(action :update :patch handler))
put-action (when-let [handler (:put controller)]
(action :put :put handler))
destroy-action (when-let [handler (:destroy controller)]
(action :destroy :delete handler))
middleware (:middleware controller)]
(composite
(resources-collection-scope plural-name segment
index-action)
(resources-collection-scope singular-name segment
new-action
create-action)
(apply resources-member-scope plural-name singular-name segment middleware
(into nested
[show-action
edit-action
update-action
put-action
destroy-action]))))))

;; ~~~~~~~~~~ Resource ~~~~~~~~~~

Expand All @@ -181,41 +238,44 @@
(fn [req]
(update req ::segments conj segment))
identity)]
(scope scope-id handle-impl fill-impl children)))
(scope scope-id handle-impl fill-impl identity children)))

(defn resource [singular-name controller & {:keys [segment nested]
:or {segment (name singular-name)
nested []}}]
(let [new-action (when-let [handler (:new controller)]
(action :new :get "new" handler))
create-action (when-let [handler (:create controller)]
(action :create :post handler))
show-action (when-let [handler (:show controller)]
(action :show :get handler))
edit-action (when-let [handler (:edit controller)]
(action :edit :get "edit" handler))
update-action (when-let [handler (:update controller)]
(action :update :patch handler))
put-action (when-let [handler (:put controller)]
(action :put :put handler))
destroy-action (when-let [handler (:destroy controller)]
(action :destroy :delete handler))]
(apply resource-scope singular-name segment
new-action
create-action
show-action
edit-action
update-action
put-action
destroy-action
nested)))
(defn resource [& args]
(let [[singular-name controller
{:keys [segment], :or {segment (name singular-name)}}
nested]
(parse-args 2 args)]
(let [new-action (when-let [handler (:new controller)]
(action :new :get "new" handler))
create-action (when-let [handler (:create controller)]
(action :create :post handler))
show-action (when-let [handler (:show controller)]
(action :show :get handler))
edit-action (when-let [handler (:edit controller)]
(action :edit :get "edit" handler))
update-action (when-let [handler (:update controller)]
(action :update :patch handler))
put-action (when-let [handler (:put controller)]
(action :put :put handler))
destroy-action (when-let [handler (:destroy controller)]
(action :destroy :delete handler))]
(apply resource-scope singular-name segment
new-action
create-action
show-action
edit-action
update-action
put-action
destroy-action
nested))))

;; ~~~~~~~~~~ Helpers ~~~~~~~~~~

(def ^:private empty-segments #?(:clj clojure.lang.PersistentQueue/EMPTY
:cljs cljs.core/PersistentQueue.EMPTY))
(def ^:private empty-scope #?(:clj clojure.lang.PersistentQueue/EMPTY
:cljs cljs.core/PersistentQueue.EMPTY))
(def ^:private empty-middlewares [])

(defn- uri->segments [uri]
(into empty-segments
Expand All @@ -232,6 +292,7 @@
(assoc r ::scope empty-scope)
(assoc r ::params {})
(assoc r ::segments (uri->segments (:uri r)))
(assoc r ::middlewares empty-middlewares)
(handle item r))))

(defn make-request-for [item]
Expand Down
74 changes: 72 additions & 2 deletions test/darkleaf/router_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@

pages-controller {}
pages (r/resources :pages :page pages-controller
:nested [comments])
comments)
pages-testing (partial route-testing pages)]
(pages-testing :show [:page :comment] {:page-id "some-page-id"
:comment-id "some-comment-id"}
Expand All @@ -150,7 +150,7 @@

star-controller {}
star (r/resource :star star-controller
:nested [comments])
comments)
star-testing (partial route-testing star)]
(star-testing :show [:star :comment] {:comment-id "some-comment-id"}
{:uri "/star/comments/some-comment-id"
Expand All @@ -166,3 +166,73 @@
admin-testing (partial route-testing admin)]
(admin-testing :index [:admin :pages] {}
{:uri "/admin/pages", :request-method :get})))

;; ~~~~~~~~~~ Middlewares ~~~~~~~~~~

(deftest resources-with-middleware
(let [comments-controller {:middleware (fn [handler]
(fn [req]
(-> req
(assoc :test-key :overriden)
(handler))))
:show (fn [req] req)
:index (fn [req] req)}
comments (r/resources :comments :comment comments-controller)

pages-controller {:middleware (fn [handler]
(fn [req]
(-> req
(assoc :test-key :original)
(handler))))
:index (fn [req] req)
:show (fn [req] req)}
pages (r/resources :pages :page pages-controller
comments)
handler (r/make-handler pages)]
(testing "head resources"
(let [req {:uri "/pages", :request-method :get}
resp (handler req)]
(is (not (contains? resp :test-key))))
(let [req {:uri "/pages/1", :request-method :get}
resp (handler req)]
(is (= :original (:test-key resp)))))
(testing "nested"
(let [req {:uri "/pages/1/comments", :request-method :get}
resp (handler req)]
(is (= :original (:test-key resp))))
(let [req {:uri "/pages/1/comments/1", :request-method :get}
resp (handler req)]
(is (= :overriden (:test-key resp)))))))

(deftest section-with-midleware
(let [pages-controller {:index (fn [req] req)}
pages (r/resources :pages :page pages-controller)
middleware (fn [handler]
(fn [req]
(-> req
(assoc :test-key true)
(handler))))
routes (r/section :admin
:middleware middleware
pages)
handler (r/make-handler routes)]
(testing :index
(let [req {:uri "/admin/pages", :request-method :get}
resp (handler req)]
(is (contains? resp :test-key))))))

(deftest wrapper
(let [pages-controller {:index (fn [req] req)}
pages (r/resources :pages :page pages-controller)
middleware (fn [handler]
(fn [req]
(-> req
(assoc :test-key true)
(handler))))
routes (r/wrapper middleware
pages)
handler (r/make-handler routes)]
(testing :index
(let [req {:uri "/pages", :request-method :get}
resp (handler req)]
(is (contains? resp :test-key))))))

0 comments on commit 9ae61b3

Please sign in to comment.