diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc
index e1ef8321..d2d4384a 100644
--- a/modules/reitit-http/src/reitit/http.cljc
+++ b/modules/reitit-http/src/reitit/http.cljc
@@ -77,7 +77,8 @@
(interceptor/queue executor))
router-opts (-> (r/options router)
(assoc ::interceptor/queue (partial interceptor/queue executor))
- (update-in [:data :interceptors] (partial into (vec interceptors))))
+ (cond-> (seq interceptors)
+ (update-in [:data :interceptors] (partial into (vec interceptors)))))
router (reitit.http/router (r/routes router) router-opts)]
(with-meta
(fn
@@ -91,7 +92,8 @@
(impl/fast-assoc :path-params path-params)
(impl/fast-assoc ::r/match match)
(impl/fast-assoc ::r/router router))]
- (interceptor/execute executor interceptors request))
+ (or (interceptor/execute executor interceptors request)
+ (interceptor/execute executor default-queue request)))
(interceptor/execute executor default-queue request)))
([request respond raise]
(if-let [match (r/match-by-path router (:uri request))]
diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc
index 6e6a3b90..f74b0e85 100644
--- a/modules/reitit-ring/src/reitit/ring.cljc
+++ b/modules/reitit-ring/src/reitit/ring.cljc
@@ -109,7 +109,7 @@
(if-let [match (::r/match request)]
(let [method (:request-method request :any)
result (:result match)
- handler? (or (-> result method :handler) (-> result :any :handler))
+ handler? (or (-> result method) (-> result :any))
error-handler (if handler? not-acceptable method-not-allowed)]
(error-handler request))
(not-found request)))
diff --git a/project.clj b/project.clj
index 5bd53d69..8bcb20b0 100644
--- a/project.clj
+++ b/project.clj
@@ -27,7 +27,7 @@
[metosin/ring-swagger-ui "2.2.10"]
[metosin/muuntaja "0.6.0-alpha1"]
[metosin/jsonista "0.2.1"]
- [metosin/sieppari "0.0.0-alpha1"]]
+ [metosin/sieppari "0.0.0-alpha3"]]
:plugins [[jonase/eastwood "0.2.6"]
[lein-doo "0.1.10"]
@@ -64,7 +64,7 @@
[ikitommi/immutant-web "3.0.0-alpha1"]
[metosin/muuntaja "0.6.0-alpha1"]
[metosin/ring-swagger-ui "2.2.10"]
- [metosin/sieppari "0.0.0-alpha1"]
+ [metosin/sieppari "0.0.0-alpha3"]
[metosin/jsonista "0.2.1"]
[criterium "0.4.4"]
@@ -83,6 +83,7 @@
[ikitommi/immutant-web "3.0.0-alpha1"]
[io.pedestal/pedestal.route "0.5.4"]
[org.clojure/core.async "0.4.474"]
+ [metosin/sieppari "0.0.0-alpha3"]
[yada "1.2.13"]
[ring/ring-defaults "0.3.1"]
[ataraxy "0.4.0"]
diff --git a/test/cljc/reitit/http_test.cljc b/test/cljc/reitit/http_test.cljc
new file mode 100644
index 00000000..79fd20b7
--- /dev/null
+++ b/test/cljc/reitit/http_test.cljc
@@ -0,0 +1,424 @@
+(ns reitit.http-test
+ (:require [clojure.test :refer [deftest testing is]]
+ [clojure.set :as set]
+ [reitit.interceptor :as interceptor]
+ [reitit.interceptor.sieppari :as sieppari]
+ [reitit.http :as http]
+ [reitit.ring :as ring]
+ [reitit.core :as r])
+ #?(:clj
+ (:import (clojure.lang ExceptionInfo))))
+
+(defn interceptor [name]
+ {:enter (fn [ctx] (update-in ctx [:request ::i] (fnil conj []) name))})
+
+(defn handler [{:keys [::i]}]
+ {:status 200 :body (conj i :ok)})
+
+(deftest http-router-test
+
+ (testing "http-handler"
+ (let [api-interceptor (interceptor :api)
+ router (http/router
+ ["/api" {:interceptors [api-interceptor]}
+ ["/all" handler]
+ ["/get" {:get handler}]
+ ["/users" {:interceptors [[interceptor :users]]
+ :get handler
+ :post {:handler handler
+ :interceptors [[interceptor :post]]}
+ :handler handler}]])
+ app (http/ring-handler router nil {:executor sieppari/executor})]
+
+ (testing "router can be extracted"
+ (is (= (r/routes router)
+ (r/routes (http/get-router app)))))
+
+ (testing "not found"
+ (is (= nil (app {:uri "/favicon.ico"}))))
+
+ (testing "catch all handler"
+ (is (= {:status 200, :body [:api :ok]}
+ (app {:uri "/api/all" :request-method :get}))))
+
+ (testing "just get handler"
+ (is (= {:status 200, :body [:api :ok]}
+ (app {:uri "/api/get" :request-method :get})))
+ (is (= nil (app {:uri "/api/get" :request-method :post}))))
+
+ (testing "expanded method handler"
+ (is (= {:status 200, :body [:api :users :ok]}
+ (app {:uri "/api/users" :request-method :get}))))
+
+ (testing "method handler with middleware"
+ (is (= {:status 200, :body [:api :users :post :ok]}
+ (app {:uri "/api/users" :request-method :post}))))
+
+ (testing "fallback handler"
+ (is (= {:status 200, :body [:api :users :ok]}
+ (app {:uri "/api/users" :request-method :put}))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result), raise ::not-called]
+ (app {:uri "/api/users" :request-method :post} respond raise)
+ (is (= {:status 200, :body [:api :users :post :ok]}
+ @result))))))
+
+ (testing "named routes"
+ (let [router (http/router
+ [["/api"
+ ["/all" {:handler handler :name ::all}]
+ ["/get" {:get {:handler handler :name ::HIDDEN}
+ :name ::get}]
+ ["/users" {:get handler
+ :post handler
+ :handler handler
+ :name ::users}]]])
+ app (http/ring-handler router nil {:executor sieppari/executor})]
+
+ (testing "router can be extracted"
+ (is (= (r/routes router)
+ (r/routes (http/get-router app)))))
+
+ (testing "only top-level route names are matched"
+ (is (= [::all ::get ::users]
+ (r/route-names router))))
+
+ (testing "all named routes can be matched"
+ (doseq [name (r/route-names router)]
+ (is (= name (-> (r/match-by-name router name) :data :name))))))))
+
+(def enforce-roles-interceptor
+ {:enter (fn [{{:keys [::roles] :as request} :request :as ctx}]
+ (let [required (some-> request (http/get-match) :data ::roles)]
+ (if (and (seq required) (not (set/intersection required roles)))
+ (-> ctx
+ (assoc :response {:status 403, :body "forbidden"})
+ (assoc :queue nil))
+ ctx)))})
+
+(deftest enforcing-data-rules-at-runtime-test
+ (let [handler (constantly {:status 200, :body "ok"})
+ app (http/ring-handler
+ (http/router
+ [["/api"
+ ["/ping" handler]
+ ["/admin" {::roles #{:admin}}
+ ["/ping" handler]]]]
+ {:data {:interceptors [enforce-roles-interceptor]}})
+ nil {:executor sieppari/executor})]
+
+ (testing "public handler"
+ (is (= {:status 200, :body "ok"}
+ (app {:uri "/api/ping" :request-method :get}))))
+
+ (testing "runtime-enforced handler"
+ (testing "without needed roles"
+ (is (= {:status 403 :body "forbidden"}
+ (app {:uri "/api/admin/ping"
+ :request-method :get}))))
+ (testing "with needed roles"
+ (is (= {:status 200, :body "ok"}
+ (app {:uri "/api/admin/ping"
+ :request-method :get
+ ::roles #{:admin}})))))))
+
+(deftest default-handler-test
+ (let [response {:status 200, :body "ok"}
+ router (http/router
+ [["/ping" {:get (constantly response)}]
+ ["/pong" (constantly nil)]])
+ app (http/ring-handler router nil {:executor sieppari/executor})]
+
+ (testing "match"
+ (is (= response (app {:request-method :get, :uri "/ping"}))))
+
+ (testing "no match"
+ (testing "with defaults"
+ (testing "route doesn't match yields nil"
+ (is (= nil (app {:request-method :get, :uri "/"}))))
+ (testing "method doesn't match yields nil"
+ (is (= nil (app {:request-method :post, :uri "/ping"}))))
+ (testing "handler rejects yields nil"
+ (is (= nil (app {:request-method :get, :uri "/pong"})))))
+
+ (testing "with default http responses"
+ (let [app (http/ring-handler
+ router
+ (ring/create-default-handler)
+ {:executor sieppari/executor})]
+ (testing "route doesn't match yields 404"
+ (is (= 404 (:status (app {:request-method :get, :uri "/"})))))
+ (testing "method doesn't match yields 405"
+ (is (= 405 (:status (app {:request-method :post, :uri "/ping"})))))
+ (testing "handler rejects yields nil"
+ (is (= 406 (:status (app {:request-method :get, :uri "/pong"})))))))
+
+ (testing "with custom http responses"
+ (let [app (http/ring-handler
+ router
+ (ring/create-default-handler
+ {:not-found (constantly {:status -404})
+ :method-not-allowed (constantly {:status -405})
+ :not-acceptable (constantly {:status -406})})
+ {:executor sieppari/executor})]
+ (testing "route doesn't match"
+ (is (= -404 (:status (app {:request-method :get, :uri "/"})))))
+ (testing "method doesn't match"
+ (is (= -405 (:status (app {:request-method :post, :uri "/ping"})))))
+ (testing "handler rejects"
+ (is (= -406 (:status (app {:request-method :get, :uri "/pong"}))))))))))
+
+#_(deftest async-http-test
+ (let [promise #(let [value (atom ::nil)]
+ (fn
+ ([] @value)
+ ([x] (reset! value x))))
+ response {:status 200, :body "ok"}
+ router (http/router
+ [["/ping" {:get (fn [_ respond _]
+ (respond response))}]
+ ["/pong" (fn [_ respond _]
+ (respond nil))]])
+ app (http/ring-handler router)]
+
+ (testing "match"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :get, :uri "/ping"} respond raise)
+ (is (= response (respond)))
+ (is (= ::nil (raise)))))
+
+ (testing "no match"
+
+ (testing "with defaults"
+ (testing "route doesn't match"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :get, :uri "/"} respond raise)
+ (is (= nil (respond)))
+ (is (= ::nil (raise)))))
+ (testing "method doesn't match"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :post, :uri "/ping"} respond raise)
+ (is (= nil (respond)))
+ (is (= ::nil (raise)))))
+ (testing "handler rejects"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :get, :uri "/pong"} respond raise)
+ (is (= nil (respond)))
+ (is (= ::nil (raise))))))
+
+ (testing "with default http responses"
+
+ (let [app (http/ring-handler router (ring/create-default-handler))]
+ (testing "route doesn't match"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :get, :uri "/"} respond raise)
+ (is (= 404 (:status (respond))))
+ (is (= ::nil (raise)))))
+ (testing "method doesn't match"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :post, :uri "/ping"} respond raise)
+ (is (= 405 (:status (respond))))
+ (is (= ::nil (raise)))))
+ (testing "if handler rejects"
+ (let [respond (promise)
+ raise (promise)]
+ (app {:request-method :get, :uri "/pong"} respond raise)
+ (is (= 406 (:status (respond))))
+ (is (= ::nil (raise))))))))))
+
+#_(deftest middleware-transform-test
+ (let [middleware (fn [name] {:name name
+ :wrap (fn [handler]
+ (fn [request]
+ (handler (update request ::mw (fnil conj []) name))))})
+ handler (fn [{:keys [::mw]}] {:status 200 :body (conj mw :ok)})
+ request {:uri "/api/avaruus" :request-method :get}
+ create (fn [options]
+ (http/ring-handler
+ (http/router
+ ["/api" {:interceptors [(middleware :olipa)]}
+ ["/avaruus" {:interceptors [(middleware :kerran)]
+ :get {:handler handler
+ :interceptors [(middleware :avaruus)]}}]]
+ options)))]
+
+ (testing "by default, all middleware are applied in order"
+ (let [app (create nil)]
+ (is (= {:status 200, :body [:olipa :kerran :avaruus :ok]}
+ (app request)))))
+
+ (testing "middleware can be re-ordered"
+ (let [app (create {::interceptor/transform (partial sort-by :name)})]
+ (is (= {:status 200, :body [:avaruus :kerran :olipa :ok]}
+ (app request)))))
+
+ (testing "adding debug middleware between middleware"
+ (let [app (create {::interceptor/transform #(interleave % (repeat (middleware "debug")))})]
+ (is (= {:status 200, :body [:olipa "debug" :kerran "debug" :avaruus "debug" :ok]}
+ (app request)))))))
+
+#?(:clj
+ (deftest resource-handler-test
+ (let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}})
+ request (fn [uri] {:uri uri, :request-method :get})]
+ (testing "inside a router"
+
+ (testing "from root"
+ (let [app (http/ring-handler
+ (http/router
+ ["/*" (ring/create-resource-handler)])
+ (ring/create-default-handler)
+ {:executor sieppari/executor})]
+ (testing test
+ (testing "different file-types"
+ (let [response (app (request "/hello.json"))]
+ (is (= "application/json" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
+ (let [response (app (request "/hello.xml"))]
+ (is (= "text/xml" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body response))))))
+
+ (testing "index-files"
+ (let [response (app (request "/docs"))]
+ (is (= (redirect "/docs/index.html") response)))
+ (let [response (app (request "/docs/"))]
+ (is (= (redirect "/docs/index.html") response))))
+
+ (testing "not found"
+ (let [response (app (request "/not-found"))]
+ (is (= 404 (:status response)))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result)
+ raise ::not-called]
+ (app (request "/hello.xml") respond raise)
+ (is (= "text/xml" (get-in @result [:headers "Content-Type"])))
+ (is (get-in @result [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body @result)))))))))
+
+ (testing "from path"
+ (let [app (http/ring-handler
+ (http/router
+ ["/files/*" (ring/create-resource-handler)])
+ (ring/create-default-handler)
+ {:executor sieppari/executor})
+ request #(request (str "/files" %))
+ redirect #(redirect (str "/files" %))]
+ (testing test
+ (testing "different file-types"
+ (let [response (app (request "/hello.json"))]
+ (is (= "application/json" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
+ (let [response (app (request "/hello.xml"))]
+ (is (= "text/xml" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body response))))))
+
+ (testing "index-files"
+ (let [response (app (request "/docs"))]
+ (is (= (redirect "/docs/index.html") response)))
+ (let [response (app (request "/docs/"))]
+ (is (= (redirect "/docs/index.html") response))))
+
+ (testing "not found"
+ (let [response (app (request "/not-found"))]
+ (is (= 404 (:status response)))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result)
+ raise ::not-called]
+ (app (request "/hello.xml") respond raise)
+ (is (= "text/xml" (get-in @result [:headers "Content-Type"])))
+ (is (get-in @result [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body @result))))))))))
+
+ (testing "outside a router"
+
+ (testing "from root"
+ (let [app (http/ring-handler
+ (http/router [])
+ (ring/routes
+ (ring/create-resource-handler {:path "/"})
+ (ring/create-default-handler))
+ {:executor sieppari/executor})]
+ (testing test
+ (testing "different file-types"
+ (let [response (app (request "/hello.json"))]
+ (is (= "application/json" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
+ (let [response (app (request "/hello.xml"))]
+ (is (= "text/xml" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body response))))))
+
+ (testing "index-files"
+ (let [response (app (request "/docs"))]
+ (is (= (redirect "/docs/index.html") response)))
+ (let [response (app (request "/docs/"))]
+ (is (= (redirect "/docs/index.html") response))))
+
+ (testing "not found"
+ (let [response (app (request "/not-found"))]
+ (is (= 404 (:status response)))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result)
+ raise ::not-called]
+ (app (request "/hello.xml") respond raise)
+ (is (= "text/xml" (get-in @result [:headers "Content-Type"])))
+ (is (get-in @result [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body @result)))))))))
+
+ (testing "from path"
+ (let [app (http/ring-handler
+ (http/router [])
+ (ring/routes
+ (ring/create-resource-handler {:path "/files"})
+ (ring/create-default-handler))
+ {:executor sieppari/executor})
+ request #(request (str "/files" %))
+ redirect #(redirect (str "/files" %))]
+ (testing test
+ (testing "different file-types"
+ (let [response (app (request "/hello.json"))]
+ (is (= "application/json" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
+ (let [response (app (request "/hello.xml"))]
+ (is (= "text/xml" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body response))))))
+
+ (testing "index-files"
+ (let [response (app (request "/docs"))]
+ (is (= (redirect "/docs/index.html") response)))
+ (let [response (app (request "/docs/"))]
+ (is (= (redirect "/docs/index.html") response))))
+
+ (testing "not found"
+ (let [response (app (request "/not-found"))]
+ (is (= 404 (:status response)))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result)
+ raise ::not-called]
+ (app (request "/hello.xml") respond raise)
+ (is (= "text/xml" (get-in @result [:headers "Content-Type"])))
+ (is (get-in @result [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body @result)))))))))))))