diff --git a/src/puppetlabs/comidi.clj b/src/puppetlabs/comidi.clj index 79433c5..a8035e0 100644 --- a/src/puppetlabs/comidi.clj +++ b/src/puppetlabs/comidi.clj @@ -276,20 +276,37 @@ [method pattern bindings body] `[~pattern {~method (handler-fn* ~bindings ~body)}]) +(defn route-tree-zip + "Returns a zipper for a bidi route tree i.e. for an arbitrarily nested structure of + `bidi.schema/RoutePair`s" + [root] + (zip/zipper + (fn [[_ matched]] + (or (vector? matched) (map? matched))) + + (fn [[_ matched]] + (seq matched)) + + (fn [[pattern matched :as node] children] + (with-meta + [pattern (into (if (vector? matched) [] {}) children)] + (meta node))) + + root)) + (defn wrap-routes* "Help function, used by compojure-like wrap-routes function to wrap leaf handlers in the bidi route with the middleware" [loc middleware] - (let [node (zip/node loc) - loc (cond - (fn? node) (zip/replace loc (middleware node)) - (map? node) (zip/replace - loc - (reduce-kv (fn [m k v] (assoc m k (middleware v))) {} node)) - :else loc)] - (if (zip/end? loc) - loc - (wrap-routes* (zip/next loc) middleware)))) + (if (zip/end? loc) + loc + (let [loc (if (zip/branch? loc) ; we only want modify the leaf nodes + loc + (let [[pattern matched] (zip/node loc)] + (if (fn? matched) + (zip/replace loc [pattern (middleware matched)]) + loc)))] + (recur (zip/next loc) middleware)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Public - core functions @@ -328,7 +345,7 @@ [routes :- bidi-schema/RoutePair middleware :- (schema/pred fn?)] (-> routes - zip/vector-zip + route-tree-zip (wrap-routes* middleware) zip/root)) diff --git a/test/puppetlabs/comidi_test.clj b/test/puppetlabs/comidi_test.clj index 4a9dc7f..0b3a53a 100644 --- a/test/puppetlabs/comidi_test.clj +++ b/test/puppetlabs/comidi_test.clj @@ -3,7 +3,8 @@ [puppetlabs.comidi :as comidi :refer :all] [schema.test :as schema-test] [schema.core :as schema] - [clojure.zip :as zip])) + [clojure.zip :as zip] + [bidi.bidi :as bidi])) (use-fixtures :once schema-test/validate-schemas) @@ -359,31 +360,38 @@ dd-route (DELETE "/dd" request "dd!") ee-route (GET "/ee" request "ee!") ff-route (ANY "/ff" request "ff!") + gh-route (ANY (bidi/alts "/gg" "/hh") request "gg-or-hh!") left-routes (context "/left" aa-route bb-route) middle-routes (context "/middle" cc-route dd-route) right-routes (context "/right" ee-route ff-route) - handler (-> (routes left-routes middle-routes right-routes) routes->handler)] + alternate-routes ["/alts" [gh-route]] + handler (-> (routes left-routes middle-routes right-routes alternate-routes) routes->handler)] (testing "Routes without middleware applied" (is (= (:body (handler {:uri "/left/aa" :request-method :get})) "aa!")) (is (= (:body (handler {:uri "/left/bb" :request-method :post})) "bb!")) (is (= (:body (handler {:uri "/middle/cc" :request-method :get})) "cc!")) (is (= (:body (handler {:uri "/middle/dd" :request-method :delete})) "dd!")) (is (= (:body (handler {:uri "/right/ee" :request-method :get})) "ee!")) - (is (= (:body (handler {:uri "/right/ff" :request-method :delete})) "ff!"))) + (is (= (:body (handler {:uri "/right/ff" :request-method :delete})) "ff!")) + (is (= (:body (handler {:uri "/alts/gg" :request-method :put})) "gg-or-hh!")) + (is (= (:body (handler {:uri "/alts/hh" :request-method :post})) "gg-or-hh!")) + (is (= (:body (handler {:uri "/alts/ii" :request-method :post})) nil))) (testing "Routes but now with middleware applied" (let [wrapped-bb-route (-> bb-route (wrap-routes bb-wrapper-middleware)) left-routes (-> (context "/left" aa-route wrapped-bb-route) (wrap-routes inner-middleware) (wrap-routes outer-middleware)) - middle-routes (context "/middle" cc-route dd-route) - right-routes (-> (context "/right" ee-route ff-route) (wrap-routes outer-middleware)) - handler (-> (routes left-routes middle-routes right-routes) routes->handler)] + right-routes (-> right-routes (wrap-routes outer-middleware)) + alternate-routes (-> alternate-routes (wrap-routes inner-middleware) (wrap-routes outer-middleware)) + handler (-> (routes left-routes middle-routes right-routes alternate-routes) routes->handler)] (is (= (:body (handler {:uri "/left/aa" :request-method :get})) "outer-inner-aa!")) (is (= (:body (handler {:uri "/left/bb" :request-method :post})) "outer-inner-bb-wrapper-bb!")) (is (= (:body (handler {:uri "/middle/cc" :request-method :get})) "cc!")) (is (= (:body (handler {:uri "/middle/dd" :request-method :delete})) "dd!")) (is (= (:body (handler {:uri "/right/ee" :request-method :get})) "outer-ee!")) - (is (= (:body (handler {:uri "/right/ff" :request-method :delete})) "outer-ff!")))))) + (is (= (:body (handler {:uri "/right/ff" :request-method :delete})) "outer-ff!")) + (is (= (:body (handler {:uri "/alts/gg" :request-method :delete})) "outer-inner-gg-or-hh!")) + (is (= (:body (handler {:uri "/alts/hh" :request-method :delete})) "outer-inner-gg-or-hh!")))))) (deftest destructuring-test (testing "Compojure-style destructuring works as expected"