diff options
-rw-r--r-- | src/reverse_routing/core.clj | 107 | ||||
-rw-r--r-- | test/reverse_routing/core_test.clj | 50 |
2 files changed, 94 insertions, 63 deletions
diff --git a/src/reverse_routing/core.clj b/src/reverse_routing/core.clj index 4936064..cab8f33 100644 --- a/src/reverse_routing/core.clj +++ b/src/reverse_routing/core.clj @@ -1,82 +1,63 @@ (ns reverse-routing.core - (:require [clojure.string :as string])) + (:require clout.core + compojure.core + [clojure.string :as string])) + +(def ^:private ^:dynamic *reverse-routes* nil) +(def ^:private ^:dynamic *root* nil) (defn wrap-reverse-routing [handler & {:keys [root] :or {:root ""}}] (fn [request] - (let [route-table (->> handler meta ::routes) - request (assoc request - ::reverse-routes route-table - ::root root)] + (binding [*reverse-routes* (->> handler meta ::routes) + *root* root] (handler request)))) (defn ^:private routing [request & handlers] (some #(% request) handlers)) -(defn routes [& handlers] - (let [merge-first (partial merge-with (fn [x y] x))] - (apply vary-meta #(apply routing % handlers) - update-in [::routes] - merge-first (map (comp ::routes meta) handlers)))) +(defmacro routes [& handlers] + (let [routes-map (->> handlers + (map (comp ::routes meta macroexpand)) + (into {}))] + (vary-meta `(vary-meta (fn [request#] + (#'routing request# ~@handlers)) + assoc ::routes ~routes-map) + assoc ::routes routes-map))) + +(defmacro defroutes [name & handlers] + `(def ~name (routes ~@handlers))) (defmacro context [path args & routes] - (let [string-path (if (vector? path) (first path) path)] - `(with-meta (compojure.core/context ~path ~args ~@routes) - {::routes (let [path-keys# (:keys (clout.core/route-compile ~string-path)) - ~args [] ;; wow. could this be hackier? I doubt it. - ] - (->> (for [routes# (map (comp ::routes meta) (list ~@routes)) - [key# val#] routes#] - [key# {:uri (str ~string-path (:uri val#)) - :args (concat path-keys# (:args val#)) - :type (:type val#)}]) - (into {})))}))) + (let [string-path (if (vector? path) (first path) path) + routes-map (let [path-keys (:keys (clout.core/route-compile string-path)) + keylen (count path-keys)] + (->> (for [route (map (comp ::routes meta macroexpand) routes) + [[path num] {:keys [uri args type]}] route] + [[path (+ num keylen)] {:uri (str string-path uri) + :args (vec (concat path-keys args)) + :type type}]) + (into {})))] + (vary-meta `(vary-meta (compojure.core/context ~path ~args ~@routes) + assoc ::routes ~routes-map) + assoc ::routes routes-map))) (defmacro register-route [route-name [type path args & body :as route]] - (let [string-path (if (vector? path) (first path) path)] - `(with-meta ~route - {::routes {~route-name {:uri ~string-path - :type ~(keyword (string/lower-case (name type))) - :args (:keys (clout.core/route-compile ~string-path))}}}))) - -(defn url-for [request route & arg-values] - (let [{:keys [uri type args]} (-> request ::reverse-routes route) - root-path (::root request)] + (let [string-path (if (vector? path) (first path) path) + route-args (:keys (clout.core/route-compile string-path)) + route-id [route-name (count route-args)] + routes-map {route-id {:uri string-path + :type (keyword (string/lower-case (name type))) + :args (vec route-args)}}] + (vary-meta `(vary-meta ~route assoc ::routes ~routes-map) + assoc ::routes routes-map))) + +(defn url-for [route & arg-values] + (let [{:keys [uri type args]} (get *reverse-routes* [route (count arg-values)]) + root-path *root*] + (assert uri) (str root-path (reduce (fn [string [name val]] (clojure.string/replace string (str name) (str val))) uri (map vector args arg-values))))) -(comment - - (require '[compojure.core :refer [GET]]) - - (def test-routes - (-> (routes - (->> (GET "/user/:id" {{id :id} :params :as request} - (url-for request :get-something id)) - (register-route :get-user)) - (->> (GET "/person/:id" {{id :id} :params :as request} - (url-for request :get-user id)) - (register-route :get-person)) - (context "/something/:id" [id] - (GET "/nom" [] - "something, blah!") - (->> (GET "/name" {:as request} - (url-for request :get-person id)) - (register-route :get-something)) - (->> (GET ["/nam/:blah", :blah #"1\d+"] {{bloo :blah} :params :as request} - (url-for request :get-something-else id 10)) - (register-route :get-something-else)))) - wrap-reverse-routing)) - (test-routes {:request-method :get - :scheme :http - ;;:uri "/something/15/name" - ;;:uri "/person/15" - :uri "/user/15" - ;;:uri "/something/15/nam/12" - ;;:uri "/something/10/nom" - :remote-addr "127.0.0.1" - :server-port 8080 - :server-name "something"}) - ) diff --git a/test/reverse_routing/core_test.clj b/test/reverse_routing/core_test.clj new file mode 100644 index 0000000..d611dea --- /dev/null +++ b/test/reverse_routing/core_test.clj @@ -0,0 +1,50 @@ +(ns reverse-routing.core-test + (:use clojure.test + reverse-routing.core + [compojure.core :only [GET]])) + +(defn make-request [handler uri] + (-> {:request-method :get + :scheme :http + :uri uri + :remote-addr "127.0.0.1" + :server-port 8080 + :server-name "something"} + handler + :body)) + +(deftest test-basic-route + (let [handler (-> (routes + (register-route :user + (GET "/user/:id" [id] (str "user " id))) + (GET "/succeed" [] (url-for :user 10)) + (GET "/fail1" [] (url-for :user)) + (GET "/fail2" [] (url-for :user 10 20))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed") "/user/10")) + (is (thrown? java.lang.AssertionError (make-request handler "/fail1"))) + (is (thrown? java.lang.AssertionError (make-request handler "/fail2"))))) + +(deftest test-contest-routes + (let [handler (-> (routes + (register-route :user + (GET "/user/" [id] (str "user list"))) + (context "/user/:id" [] + (register-route :user + (GET "/" [id] (str "user " id))) + (register-route :edit-user + (GET "/edit" [id] (str "edit user " id)))) + (GET "/succeed1" [] (url-for :user)) + (GET "/succeed2" [] (url-for :user 10)) + (GET "/succeed3" [] (url-for :edit-user 10)) + (GET "/fail1" [] (url-for :user 10 20)) + (GET "/fail2" [] (url-for :edit-user)) + (GET "/fail3" [] (url-for :edit-user 10 20))) + wrap-reverse-routing)] + (is (= (make-request handler "/succeed1") "/user/")) + (is (= (make-request handler "/succeed2") "/user/10/")) + (is (= (make-request handler "/succeed3") "/user/10/edit")) + + (is (thrown? java.lang.AssertionError (make-request handler "/fail1"))) + (is (thrown? java.lang.AssertionError (make-request handler "/fail2"))) + (is (thrown? java.lang.AssertionError (make-request handler "/fail3"))))) |