summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/reverse_routing/core.clj107
-rw-r--r--test/reverse_routing/core_test.clj50
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")))))