diff --git a/CHANGELOG.md b/CHANGELOG.md
index abebb0af..546b974d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,9 +19,21 @@
; :path "/coffee/luwak"}
```
+### `reitit-ring`
+
+* `reitit.ring/default-handler` now works correctly with async ring
+* new helper `reitit.ring/router` to compose routes outside of a router.
+* `reitit.ring/create-resource-handler` function to serve static routes. See (docs)[https://metosin.github.io/reitit/ring/static.html].
+
+* new dependencies:
+
+```clj
+[ring/ring-core "1.6.3"]
+```
+
### `reitit-swagger`
-* New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors.
+* New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors and Schema & Spec. See [docs](https://metosin.github.io/reitit/swagger.html).
```clj
(require '[reitit.ring :as ring])
@@ -40,7 +52,7 @@
["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
- :handler swagger/swagger-spec-handler}}]
+ :handler (swagger/create-swagger-handler)}}]
["/spec" {:coercion spec/coercion}
["/plus"
diff --git a/dev-resources/public/hello.json b/dev-resources/public/hello.json
new file mode 100644
index 00000000..a1cc6c1a
--- /dev/null
+++ b/dev-resources/public/hello.json
@@ -0,0 +1 @@
+{"hello": "file"}
\ No newline at end of file
diff --git a/dev-resources/public/hello.xml b/dev-resources/public/hello.xml
new file mode 100644
index 00000000..d3653a88
--- /dev/null
+++ b/dev-resources/public/hello.xml
@@ -0,0 +1 @@
+file
diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md
index aec6ecfd..2ed3f462 100644
--- a/doc/SUMMARY.md
+++ b/doc/SUMMARY.md
@@ -21,6 +21,7 @@
* [Dev Workflow](advanced/dev_workflow.md)
* [Ring](ring/README.md)
* [Ring-router](ring/ring.md)
+ * [Static Resources](ring/static.md)
* [Dynamic Extensions](ring/dynamic_extensions.md)
* [Data-driven Middleware](ring/data_driven_middleware.md)
* [Pluggable Coercion](ring/coercion.md)
@@ -28,5 +29,5 @@
* [Compiling Middleware](ring/compiling_middleware.md)
* [Performance](performance.md)
* [Interceptors (WIP)](interceptors.md)
-* [Swagger & Openapi (WIP)](openapi.md)
+* [Swagger-support](swagger.md)
* [FAQ](faq.md)
diff --git a/doc/ring/README.md b/doc/ring/README.md
index cf4968f0..d0d88756 100644
--- a/doc/ring/README.md
+++ b/doc/ring/README.md
@@ -1,6 +1,7 @@
# Ring
* [Ring-router](ring.md)
+* [Static Resources](static.md)
* [Dynamic Extensions](dynamic_extensions.md)
* [Data-driven Middleware](data_driven_middleware.md)
* [Pluggable Coercion](coercion.md)
diff --git a/doc/ring/static.md b/doc/ring/static.md
new file mode 100644
index 00000000..64beb482
--- /dev/null
+++ b/doc/ring/static.md
@@ -0,0 +1,64 @@
+# Static Resources (Clojure Only)
+
+Static resources can be served with a help of `reitit.ring/create-resource-handler`. It takes optionally an options map and returns a ring handler to serve files from Classpath. It returns `java.io.File` instances, so ring adapters can use NIO to effective Stream the files.
+
+There are two options to serve the files.
+
+## Internal routes
+
+This is good option if static files can be from non-conflicting paths, e.g. `"/assets/*"`.
+
+```clj
+(require '[reitit.ring :as ring])
+
+(ring/ring-handler
+ (ring/router
+ [["/ping" (constantly {:status 200, :body "pong"})]
+ ["/assets/*" (ring/create-resource-handler)]])
+ (ring/create-default-handler))
+```
+
+To serve static files with conflicting routes, e.g. `"/*#`, one needs to disable the conflict resolution:
+
+```clj
+(require '[reitit.ring :as ring])
+
+(ring/ring-handler
+ (ring/router
+ [["/ping" (constantly {:status 200, :body "pong"})]
+ ["/*" (ring/create-resource-handler)]]
+ {:conflicts (constantly nil)})
+ (ring/create-default-handler))
+```
+
+## External routes
+
+To serve files from conflicting paths, e.g. `"/*"`, one option is to mount them to default-handler branch of `ring-handler`. This way, they are only served if none of the actual routes have matched.
+
+
+```clj
+(ring/ring-handler
+ (ring/router
+ ["/ping" (constantly {:status 200, :body "pong"})])
+ (ring/routes
+ (ring/create-resource-handler {:path "/"})
+ (ring/create-default-handler)))
+```
+
+## Configuration
+
+`reitit.ring/create-resource-handler` takes optionally an options map to configure how the files are being served.
+
+| key | description |
+| -----------------|-------------|
+| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
+| :root | optional resource root, defaults to `"public"`
+| :path | optional path to mount the handler to. Works only if mounted outside of a router.
+| :loader | optional class loader to resolve the resources
+| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false`
+
+### TODO
+
+* support for things like `:cache`, `:last-modified?`, `:index-files` and `:gzip`
+* support for ClojureScript
+* serve from file-system
diff --git a/doc/openapi.md b/doc/swagger.md
similarity index 66%
rename from doc/openapi.md
rename to doc/swagger.md
index 914cee0e..2ddc9167 100644
--- a/doc/openapi.md
+++ b/doc/swagger.md
@@ -1,14 +1,6 @@
-# Swagger & OpenAPI (WIP)
+# Swagger
-Goal is to support both [Swagger](https://swagger.io/) & [OpenAPI](https://www.openapis.org/) for route documentation. Documentation is extracted from existing coercion definitions `:parameters`, `:responses` and from a set of new doumentation keys.
-
-Swagger-support draft works, but only for Clojure.
-
-### TODO
-
-* [metosin/schema-tools#38](https://github.com/metosin/schema-tools/issues/38): extract Schema-swagger from [ring-swagger](https://github.com/metosin/ring-swagger) into [schema-tools](https://github.com/metosin/schema-tools) to support both Clojure & ClojureScript
-* separate modules for the swagger2 & openapi
-* [metosin/spec-tools#105](https://github.com/metosin/spec-tools/issues/105): support Openapi
+Reitit supports [Swagger](https://swagger.io/) to generate route documentation. Documentation is extracted from existing coercion definitions `:parameters`, `:responses` and from a set of new doumentation keys.
### Example
@@ -25,6 +17,7 @@ Current `reitit-swagger` draft (with `reitit-ring` & data-specs):
(ring/ring-handler
(ring/router
["/api"
+
;; identify a swagger api
;; there can be several in a routing tree
{:swagger {:id :math}}
@@ -33,7 +26,14 @@ Current `reitit-swagger` draft (with `reitit-ring` & data-specs):
["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
- :handler swagger/swagger-spec-handler}}]
+ :handler (swagger/create-swagger-handler)}}]
+
+ ;; the (undocumented) swagger-ui
+ ;; [org.webjars/swagger-ui "3.13.4"]
+ ["/docs/*"
+ {:get {:no-doc true
+ :handler (ring/create-resource-handler
+ {:root "META-INF/resources/webjars/swagger-ui"})}}]
["/minus"
{:get {:summary "minus"
diff --git a/modules/reitit-ring/project.clj b/modules/reitit-ring/project.clj
index 45fb455b..059df996 100644
--- a/modules/reitit-ring/project.clj
+++ b/modules/reitit-ring/project.clj
@@ -6,4 +6,5 @@
:plugins [[lein-parent "0.3.2"]]
:parent-project {:path "../../project.clj"
:inherit [:deploy-repositories :managed-dependencies]}
- :dependencies [[metosin/reitit-core]])
+ :dependencies [[metosin/reitit-core]
+ [ring/ring-core]])
diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc
index 151d1d6a..62cde8b4 100644
--- a/modules/reitit-ring/src/reitit/ring.cljc
+++ b/modules/reitit-ring/src/reitit/ring.cljc
@@ -2,10 +2,13 @@
(:require [meta-merge.core :refer [meta-merge]]
[reitit.middleware :as middleware]
[reitit.core :as r]
- [reitit.impl :as impl]))
+ [reitit.impl :as impl]
+ #?@(:clj [
+ [ring.util.mime-type :as mime-type]
+ [ring.util.response :as response]])))
-(def http-methods #{:get :head :patch :delete :options :post :put})
-(defrecord Methods [get head post put delete trace options connect patch any])
+(def http-methods #{:get :head :post :put :delete :connect :options :trace :patch})
+(defrecord Methods [get head post put delete connect options trace patch])
(defrecord Endpoint [data handler path method middleware])
(defn- group-keys [data]
@@ -15,6 +18,22 @@
[top (assoc childs k v)]
[(assoc top k v) childs])) [{} {}] data))
+(defn routes
+ "Create a ring handler by combining several handlers into one."
+ [& handlers]
+ (let [single-arity (apply some-fn handlers)]
+ (fn
+ ([request]
+ (single-arity request))
+ ([request respond raise]
+ (letfn [(f [handlers]
+ (if (seq handlers)
+ (let [handler (first handlers)
+ respond' #(if % (respond %) (f (rest handlers)))]
+ (handler request respond' raise))
+ (respond nil)))]
+ (f handlers))))))
+
(defn create-default-handler
"A default ring handler that can handle the following cases,
configured via options:
@@ -48,6 +67,41 @@
(respond (error-handler request)))
(respond (not-found request)))))))
+#?(:clj
+ (defn create-resource-handler
+ "A ring handler for serving classpath resources, configured via options:
+
+ | key | description |
+ | -----------------|-------------|
+ | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
+ | :root | optional resource root, defaults to `\"public\"`
+ | :path | optional path to mount the handler to. Works only if mounted outside of a router.
+ | :loader | optional class loader to resolve the resources
+ | :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false`"
+ ([]
+ (create-resource-handler nil))
+ ([{:keys [parameter root path loader allow-symlinks?]
+ :or {parameter (keyword "")
+ root "public"}}]
+ (let [options {:root root, :loader loader, :allow-symlinks? allow-symlinks?}
+ path-size (count path)
+ create (fn [handler]
+ (fn
+ ([request] (handler request))
+ ([request respond _] (respond (handler request)))))
+ response (fn [path]
+ (if-let [response (response/resource-response path options)]
+ (response/content-type response (mime-type/ext-mime-type path))))
+ handler (if path
+ (fn [request]
+ (let [uri (:uri request)]
+ (if (>= (count uri) path-size)
+ (response (subs uri path-size)))))
+ (fn [request]
+ (let [path (-> request :path-params parameter)]
+ (or (response path) {:status 404}))))]
+ (create handler)))))
+
(defn ring-handler
"Creates a ring-handler out of a ring-router.
Supports both 1 (sync) and 3 (async) arities.
@@ -64,29 +118,24 @@
(let [method (:request-method request :any)
path-params (:path-params match)
result (:result match)
- handler (or (-> result method :handler)
- (-> result :any (:handler default-handler)))
+ handler (-> result method :handler (or default-handler))
request (-> request
+ (impl/fast-assoc :path-params path-params)
(impl/fast-assoc ::r/match match)
- (impl/fast-assoc ::r/router router)
- (cond-> (seq path-params) (impl/fast-assoc :path-params path-params)))
- response (handler request)]
- (if (nil? response)
- (default-handler request)
- response))
+ (impl/fast-assoc ::r/router router))]
+ (or (handler request) (default-handler request)))
(default-handler request)))
([request respond raise]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request :any)
path-params (:path-params match)
result (:result match)
- handler (or (-> result method :handler)
- (-> result :any (:handler default-handler)))
+ handler (-> result method :handler (or default-handler))
request (-> request
+ (impl/fast-assoc :path-params path-params)
(impl/fast-assoc ::r/match match)
- (impl/fast-assoc ::r/router router)
- (cond-> (seq path-params) (impl/fast-assoc :path-params path-params)))]
- (handler request respond raise))
+ (impl/fast-assoc ::r/router router))]
+ ((routes handler default-handler) request respond raise))
(default-handler request respond raise))))
{::r/router router}))))
@@ -109,14 +158,21 @@
(-> (middleware/compile-result [p d] opts s)
(map->Endpoint)
(assoc :path p)
- (assoc :method m)))]
+ (assoc :method m)))
+ ->methods (fn [any? data]
+ (reduce
+ (fn [acc method]
+ (cond-> acc
+ any? (assoc method (->endpoint path data method nil))))
+ (map->Methods {})
+ http-methods))]
(if-not (seq childs)
- (map->Methods {:any (->endpoint path top :any nil)})
+ (->methods true top)
(reduce-kv
(fn [acc method data]
(let [data (meta-merge top data)]
(assoc acc method (->endpoint path data method method))))
- (map->Methods {:any (if (:handler top) (->endpoint path data :any nil))})
+ (->methods (:handler top) data)
childs))))
(defn router
diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc
index 4ece35f3..0a296dac 100644
--- a/modules/reitit-swagger/src/reitit/swagger.cljc
+++ b/modules/reitit-swagger/src/reitit/swagger.cljc
@@ -65,24 +65,24 @@
{:name ::swagger
:spec ::spec})
-(defn swagger-spec-handler
- "Ring handler to emit swagger spec."
- [{:keys [::r/router ::r/match :request-method]}]
- (let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
- swagger (set/rename-keys swagger {:id :x-id})
- accept-route #(-> % second :swagger :id (= id))
- transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data}]]
- (if (and data (not no-doc))
- [method
- (meta-merge
- (if coercion
- (coercion/-get-apidocs coercion :swagger data))
- (select-keys data [:tags :summary :description])
- (dissoc swagger :id))]))
- transform-path (fn [[p _ c]]
- (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
- [p endpoint]))]
- (if id
- (let [paths (->> router (r/routes) (filter accept-route) (map transform-path) (into {}))]
- {:status 200
- :body (meta-merge swagger {:paths paths})}))))
+(defn create-swagger-handler []
+ "Create a ring handler to emit swagger spec."
+ (fn [{:keys [::r/router ::r/match :request-method]}]
+ (let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
+ swagger (set/rename-keys swagger {:id :x-id})
+ accept-route #(-> % second :swagger :id (= id))
+ transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data}]]
+ (if (and data (not no-doc))
+ [method
+ (meta-merge
+ (if coercion
+ (coercion/-get-apidocs coercion :swagger data))
+ (select-keys data [:tags :summary :description])
+ (dissoc swagger :id))]))
+ transform-path (fn [[p _ c]]
+ (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
+ [p endpoint]))]
+ (if id
+ (let [paths (->> router (r/routes) (filter accept-route) (map transform-path) (into {}))]
+ {:status 200
+ :body (meta-merge swagger {:paths paths})})))))
diff --git a/perf-test/clj/reitit/nodejs_perf_test.clj b/perf-test/clj/reitit/nodejs_perf_test.clj
index 512a421f..72e6dbe8 100644
--- a/perf-test/clj/reitit/nodejs_perf_test.clj
+++ b/perf-test/clj/reitit/nodejs_perf_test.clj
@@ -69,7 +69,7 @@
;; 25310 / 25126
"regex"
- ;; 84149 / 84867
+ ;; 88060 / 90778
(title "reitit")
;; wrk -d ${DURATION:="30s"} http://127.0.0.1:2048/product/foo
;; wrk -d ${DURATION:="30s"} http://127.0.0.1:2048/twenty/bar
@@ -77,5 +77,5 @@
(assert (= {:status 200, :body "Got twenty id bar"} (app {:request-method :get, :uri "/twenty/bar"}))))
(comment
- (web/run app {:port 2048})
+ (web/run app {:port 2048, :dispatch? false, :server {:always-set-keep-alive false}})
(routing-test))
diff --git a/perf-test/clj/reitit/ring_perf_test.clj b/perf-test/clj/reitit/ring_perf_test.clj
new file mode 100644
index 00000000..bc3ac653
--- /dev/null
+++ b/perf-test/clj/reitit/ring_perf_test.clj
@@ -0,0 +1,38 @@
+(ns reitit.ring-perf-test
+ (:require [criterium.core :as cc]
+ [reitit.perf-utils :refer :all]
+ [reitit.ring :as ring]))
+
+;;
+;; start repl with `lein perf repl`
+;; perf measured with the following setup:
+;;
+;; Model Name: MacBook Pro
+;; Model Identifier: MacBookPro11,3
+;; Processor Name: Intel Core i7
+;; Processor Speed: 2,5 GHz
+;; Number of Processors: 1
+;; Total Number of Cores: 4
+;; L2 Cache (per Core): 256 KB
+;; L3 Cache: 6 MB
+;; Memory: 16 GB
+;;
+
+(def app
+ (ring/ring-handler
+ (ring/router
+ [["/auth/login" identity]
+ ["/auth/recovery/token/:token" identity]
+ ["/workspace/:project/:page" identity]])))
+
+(comment
+ (let [request {:request-method :post, :uri "/auth/login"}]
+ ;; 192ns (initial)
+ ;; 163ns (always assoc path params)
+ ;; 132ns (expand methods)
+ (cc/quick-bench
+ (app request))
+
+ ;; 113ns (don't inject router)
+ ;; 89ns (don't inject router & match)
+ ))
diff --git a/perf-test/clj/reitit/static_perf_test.clj b/perf-test/clj/reitit/static_perf_test.clj
new file mode 100644
index 00000000..90cba0b1
--- /dev/null
+++ b/perf-test/clj/reitit/static_perf_test.clj
@@ -0,0 +1,107 @@
+(ns reitit.static-perf-test
+ (:require [reitit.perf-utils :refer :all]
+ [immutant.web :as web]
+ [reitit.ring :as ring]
+ [clojure.java.io :as io]
+ [criterium.core :as cc]
+ [ring.util.response]
+ [ring.middleware.defaults]
+ [ring.middleware.resource]
+ [ring.util.mime-type]))
+
+;;
+;; start repl with `lein perf repl`
+;; perf measured with the following setup:
+;;
+;; Model Name: MacBook Pro
+;; Model Identifier: MacBookPro113
+;; Processor Name: Intel Core i7
+;; Processor Speed: 2,5 GHz
+;; Number of Processors: 1
+;; Total Number of Cores: 4
+;; L2 Cache (per Core): 256 KB
+;; L3 Cache: 6 MB
+;; Memory: 16 GB
+;;
+
+(def app1
+ (ring/ring-handler
+ (ring/router
+ [["/ping" (constantly {:status 200, :body "pong"})]
+ ["/files/*" (ring/create-resource-handler)]])
+ (ring/create-default-handler)))
+
+(def app2
+ (ring/ring-handler
+ (ring/router
+ ["/ping" (constantly {:status 200, :body "pong"})])
+ (some-fn
+ (ring/create-resource-handler {:path "/files"})
+ (ring/create-default-handler))))
+
+(def wrap-resource
+ (-> (constantly {:status 200, :body "pong"})
+ (ring.middleware.resource/wrap-resource "public")))
+
+(def wrap-defaults
+ (-> (constantly {:status 200, :body "pong"})
+ (ring.middleware.defaults/wrap-defaults ring.middleware.defaults/site-defaults)))
+
+(comment
+ (def server (web/run #'app {:port 3000, :dispatch? false, :server {:always-set-keep-alive false}}))
+ (routing-test))
+
+(defn bench-resources []
+
+ ;; 134µs
+ (cc/quick-bench
+ (ring.util.response/resource-response "hello.json" {:root "public"}))
+
+ ;; 144µs
+ (cc/quick-bench
+ (app1 {:request-method :get, :uri "/files/hello.json"}))
+
+ ;; 144µs
+ (cc/quick-bench
+ (app2 {:request-method :get, :uri "/files/hello.json"}))
+
+ ;; 143µs
+ (cc/quick-bench
+ (wrap-resource {:request-method :get, :uri "/hello.json"}))
+
+ ;; 163µs
+ (cc/quick-bench
+ (wrap-defaults {:request-method :get, :uri "/hello.json"})))
+
+(defn bench-handler []
+
+ ;; 140ns
+ (cc/quick-bench
+ (app1 {:request-method :get, :uri "/ping"}))
+
+ ;; 134ns
+ (cc/quick-bench
+ (app2 {:request-method :get, :uri "/ping"}))
+
+ ;; 108µs
+ (cc/quick-bench
+ (wrap-resource {:request-method :get, :uri "/ping"}))
+
+ ;; 146µs
+ (cc/quick-bench
+ (wrap-defaults {:request-method :get, :uri "/ping"})))
+
+(comment
+ (bench-resources)
+ (bench-handler)
+
+ (let [file (-> "logback.xml" io/resource io/file)
+ name (.getName file)]
+
+ ;; 639ns
+ (cc/quick-bench
+ (ring.util.mime-type/ext-mime-type name))
+
+ ;; 106ns
+ (cc/quick-bench
+ (reitit.ring.mime/ext-mime-type name reitit.ring.mime/default-mime-types))))
diff --git a/project.clj b/project.clj
index fab2aef4..257c8415 100644
--- a/project.clj
+++ b/project.clj
@@ -17,6 +17,7 @@
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
[meta-merge "1.0.0"]
+ [ring/ring-core "1.6.3"]
[metosin/spec-tools "0.6.2-SNAPSHOT"]
[metosin/schema-tools "0.10.2-SNAPSHOT"]]
@@ -60,7 +61,8 @@
"-Dclojure.compiler.direct-linking=true"]
:test-paths ["perf-test/clj"]
:dependencies [[compojure "1.6.1"]
- [org.immutant/immutant "2.1.10"]
+ [ring/ring-defaults "0.3.1"]
+ [ikitommi/immutant-web "3.0.0-alpha1"]
[io.pedestal/pedestal.route "0.5.3"]
[org.clojure/core.async "0.4.474"]
[ataraxy "0.4.0"]
@@ -70,7 +72,7 @@
"-XX:+PrintCompilation"
"-XX:+UnlockDiagnosticVMOptions"
"-XX:+PrintInlining"]}}
- :aliases {"all" ["with-profile" "dev"]
+ :aliases {"all" ["with-profile" "dev,default"]
"perf" ["with-profile" "default,dev,perf"]
"test-clj" ["all" "do" ["bat-test"] ["check"]]
"test-browser" ["doo" "chrome-headless" "test"]
diff --git a/test/cljc/reitit/ring_test.cljc b/test/cljc/reitit/ring_test.cljc
index 83c688ef..8ef1bc9a 100644
--- a/test/cljc/reitit/ring_test.cljc
+++ b/test/cljc/reitit/ring_test.cljc
@@ -17,7 +17,7 @@
(defn handler
([{:keys [::mw]}]
{:status 200 :body (conj mw :ok)})
- ([request respond raise]
+ ([request respond _]
(respond (handler request))))
(deftest ring-router-test
@@ -227,11 +227,11 @@
(app {:request-method :post, :uri "/ping"} respond raise)
(is (= 405 (:status (respond))))
(is (= ::nil (raise)))))
- (testing "if handler rejects, nil in still returned."
+ (testing "if handler rejects"
(let [respond (promise)
raise (promise)]
(app {:request-method :get, :uri "/pong"} respond raise)
- (is (= nil (respond)))
+ (is (= 406 (:status (respond))))
(is (= ::nil (raise))))))))))
(deftest middleware-transform-test
@@ -264,3 +264,44 @@
(let [app (create {::middleware/transform #(interleave % (repeat (middleware "debug")))})]
(is (= {:status 200, :body [:olipa "debug" :kerran "debug" :avaruus "debug" :ok]}
(app request)))))))
+
+#?(:clj
+ (deftest resource-handler-test
+ (doseq [[test app] [["inside a router"
+ (ring/ring-handler
+ (ring/router
+ [["/ping" (constantly {:status 200, :body "pong"})]
+ ["/files/*" (ring/create-resource-handler)]])
+ (ring/create-default-handler))]
+
+ ["outside of a router"
+ (ring/ring-handler
+ (ring/router
+ ["/ping" (constantly {:status 200, :body "pong"})])
+ (ring/routes
+ (ring/create-resource-handler {:path "/files"})
+ (ring/create-default-handler)))]]]
+
+ (testing test
+ (testing "different file-types"
+ (let [response (app {:uri "/files/hello.json", :request-method :get})]
+ (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 {:uri "/files/hello.xml", :request-method :get})]
+ (is (= "text/xml" (get-in response [:headers "Content-Type"])))
+ (is (get-in response [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body response))))))
+
+ (testing "not found"
+ (let [response (app {:uri "/files/not-found", :request-method :get})]
+ (is (= 404 (:status response)))))
+
+ (testing "3-arity"
+ (let [result (atom nil)
+ respond (partial reset! result)
+ raise ::not-called]
+ (app {:uri "/files/hello.xml", :request-method :get} respond raise)
+ (is (= "text/xml" (get-in @result [:headers "Content-Type"])))
+ (is (get-in @result [:headers "Last-Modified"]))
+ (is (= "file\n" (slurp (:body @result))))))))))
diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj
index e7ee183e..5921fb7b 100644
--- a/test/cljc/reitit/swagger_test.clj
+++ b/test/cljc/reitit/swagger_test.clj
@@ -16,7 +16,7 @@
["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
- :handler swagger/swagger-spec-handler}}]
+ :handler (swagger/create-swagger-handler)}}]
["/spec" {:coercion spec/coercion}
["/plus"
@@ -59,7 +59,25 @@
:uri "/api/swagger.json"}))]
(is (= {:x-id ::math
:info {:title "my-api"}
- :paths {"/api/schema/plus" {:get {:summary "plus"}} ;; TODO: implement!
+ :paths {"/api/schema/plus" {:get {:parameters [{:description ""
+ :format "int32"
+ :in "query"
+ :name "x"
+ :required true
+ :type "integer"}
+ {:description ""
+ :format "int32"
+ :in "query"
+ :name "y"
+ :required true
+ :type "integer"}]
+ :responses {200 {:description ""
+ :schema {:additionalProperties false
+ :properties {"total" {:format "int32"
+ :type "integer"}}
+ :required ["total"]
+ :type "object"}}}
+ :summary "plus"}}
"/api/spec/plus" {:get {:parameters [{:description ""
:format "int64"
:in "query"