diff --git a/CHANGELOG.md b/CHANGELOG.md index e56d2723..b0c351df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ ## 0.1.2-SNAPSHOT +### `reitit-core` + +* Better handling of `nil` in route syntax: + * explicit `nil` after path string is always handled as `nil` route + * `nil` as path string causes the whole route to be `nil` + * `nil` as child route is stripped away + +```clj +(testing "nil routes are stripped" + (is (= [] (r/routes (r/router nil)))) + (is (= [] (r/routes (r/router [nil ["/ping"]])))) + (is (= [] (r/routes (r/router [nil [nil] [[nil nil nil]]])))) + (is (= [] (r/routes (r/router ["/ping" [nil "/pong"]]))))) +``` +### `reitit-ring` + +* Use HTTP redirect (302) with index-files in `reitit.ring/create-resource-handler`. +* `reitit.ring/create-default-handler` now conforms to [RING Spec](https://github.com/ring-clojure/ring/blob/master/SPEC), Fixes [#83](https://github.com/metosin/reitit/issues/83) + +https://github.com/metosin/reitit/issues/83 + ### `reitit-schema` * updated dependencies: @@ -14,6 +35,8 @@ ### `reitit-swagger-ui` +* Use HTTP redirect (302) with index-files in `reitit.swagger-ui/create-swagger-ui-handler`. + * updated dependencies: ```clj diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index 3d282a65..cc6dadc6 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -183,7 +183,7 @@ Whole example project is in [`/examples/ring-swagger`](https://github.com/metosi "application/transit+json"}}}}) (ring/routes (swagger-ui/create-swagger-ui-handler - {:path "", :url "/api/swagger.json"}) + {:path "/", :url "/api/swagger.json"}) (ring/create-default-handler)))) (defn start [] diff --git a/examples/just-coercion-with-ring/project.clj b/examples/just-coercion-with-ring/project.clj index d14fe645..e3d0513e 100644 --- a/examples/just-coercion-with-ring/project.clj +++ b/examples/just-coercion-with-ring/project.clj @@ -3,4 +3,4 @@ :dependencies [[org.clojure/clojure "1.9.0"] [ring "1.6.3"] [metosin/muuntaja "0.4.1"] - [metosin/reitit "0.1.1"]]) + [metosin/reitit "0.1.2-SNAPSHOT"]]) diff --git a/examples/ring-example/project.clj b/examples/ring-example/project.clj index 332cb4b8..31429cc4 100644 --- a/examples/ring-example/project.clj +++ b/examples/ring-example/project.clj @@ -3,4 +3,4 @@ :dependencies [[org.clojure/clojure "1.9.0"] [ring "1.6.3"] [metosin/muuntaja "0.4.1"] - [metosin/reitit "0.1.1"]]) + [metosin/reitit "0.1.2-SNAPSHOT"]]) diff --git a/examples/ring-swagger/project.clj b/examples/ring-swagger/project.clj index dfe97c89..37283a17 100644 --- a/examples/ring-swagger/project.clj +++ b/examples/ring-swagger/project.clj @@ -3,5 +3,5 @@ :dependencies [[org.clojure/clojure "1.9.0"] [ring "1.6.3"] [metosin/muuntaja "0.5.0"] - [metosin/reitit "0.1.1"]] + [metosin/reitit "0.1.2-SNAPSHOT"]] :repl-options {:init-ns example.server}) diff --git a/modules/reitit-core/project.clj b/modules/reitit-core/project.clj index 23099fba..d241b12b 100644 --- a/modules/reitit-core/project.clj +++ b/modules/reitit-core/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-core "0.1.1" +(defproject metosin/reitit-core "0.1.2-SNAPSHOT" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 35d2c87b..b3785447 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -39,14 +39,14 @@ (walk-one [pacc macc routes] (if (vector? (first routes)) (walk-many pacc macc routes) - (let [[path & [maybe-arg :as args]] routes - [data childs] (if (vector? maybe-arg) - [{} args] - [maybe-arg (rest args)]) - macc (into macc (expand data opts))] - (if (seq childs) - (walk-many (str pacc path) macc childs) - [[(str pacc path) macc]]))))] + (when (string? (first routes)) + (let [[path & [maybe-arg :as args]] routes + [data childs] (if (or (vector? maybe-arg) (nil? maybe-arg)) + [{} args] + [maybe-arg (rest args)]) + macc (into macc (expand data opts)) + child-routes (walk-many (str pacc path) macc (keep identity childs))] + (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] (walk-one path (mapv identity data) raw-routes))) (defn map-data [f routes] @@ -87,10 +87,10 @@ (conflicts-str conflicts) {:conflicts conflicts}))) -(defn name-lookup [[_ {:keys [name]}] opts] +(defn name-lookup [[_ {:keys [name]}] _] (if name #{name})) -(defn find-names [routes opts] +(defn find-names [routes _] (into [] (keep #(-> % second :name)) routes)) (defn- compile-route [[p m :as route] {:keys [compile] :as opts}] diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index d3361f0d..cf98cf24 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -9,18 +9,19 @@ (s/def ::path (s/with-gen string? #(gen/fmap (fn [s] (str "/" s)) (s/gen string?)))) -(s/def ::arg (s/and any? (complement vector?))) +(s/def ::arg (s/and some? (complement vector?))) (s/def ::data (s/map-of keyword? any?)) (s/def ::result any?) (s/def ::raw-route - (s/cat :path ::path - :arg (s/? ::arg) - :childs (s/* (s/and (s/nilable ::raw-route))))) + (s/nilable + (s/cat :path ::path + :arg (s/? ::arg) + :childs (s/* (s/and (s/nilable ::raw-routes)))))) (s/def ::raw-routes (s/or :route ::raw-route - :routes (s/coll-of ::raw-route :into []))) + :routes (s/coll-of ::raw-routes :into []))) (s/def ::route (s/cat :path ::path diff --git a/modules/reitit-ring/project.clj b/modules/reitit-ring/project.clj index 71be148e..fbb913c1 100644 --- a/modules/reitit-ring/project.clj +++ b/modules/reitit-ring/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-ring "0.1.1" +(defproject metosin/reitit-ring "0.1.2-SNAPSHOT" :description "Reitit: Ring routing" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 020ed15d..1c03e4f0 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -46,9 +46,9 @@ | `:not-acceptable` | 406, handler returned `nil`" ([] (create-default-handler - {:not-found (constantly {:status 404, :body ""}) - :method-not-allowed (constantly {:status 405, :body ""}) - :not-acceptable (constantly {:status 406, :body ""})})) + {:not-found (constantly {:status 404, :body "", :headers {}}) + :method-not-allowed (constantly {:status 405, :body "", :headers {}}) + :not-acceptable (constantly {:status 406, :body "", :headers {}})})) ([{:keys [not-found method-not-allowed not-acceptable]}] (fn ([request] @@ -96,24 +96,30 @@ (fn ([request] (handler request)) ([request respond _] (respond (handler request))))) + join-paths (fn [& paths] + (str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" "")) resource-response (fn [path] - (if-let [response (or (paths path) (response/resource-response path options))] + (if-let [response (or (paths (join-paths "/" path)) + (response/resource-response path options))] (response/content-type response (mime-type/ext-mime-type path)))) - path-or-index-response (fn [path] + path-or-index-response (fn [path uri] (or (resource-response path) - (let [separator (if-not (str/ends-with? path "/") "/")] - (loop [[file & files] index-files] - (if file - (or (resource-response (str path separator file)) - (recur files))))))) + (loop [[file & files] index-files] + (if file + (if (resource-response (join-paths path file)) + (response/redirect (join-paths uri file)) + (recur files)))))) handler (if path (fn [request] (let [uri (:uri request)] (if-let [path (if (>= (count uri) path-size) (subs uri path-size))] - (path-or-index-response path)))) + (path-or-index-response path uri)))) (fn [request] - (let [path (-> request :path-params parameter)] - (or (path-or-index-response path) {:status 404}))))] + (let [uri (:uri request) + path (-> request :path-params parameter)] + (or (path-or-index-response path uri) + ;; TODO: use generic not-found handler + {:status 404}))))] (create handler))))) (defn ring-handler @@ -129,7 +135,7 @@ (fn ([request] (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request :any) + (let [method (:request-method request) path-params (:path-params match) result (:result match) handler (-> result method :handler (or default-handler)) @@ -141,7 +147,7 @@ (default-handler request))) ([request respond raise] (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request :any) + (let [method (:request-method request) path-params (:path-params match) result (:result match) handler (-> result method :handler (or default-handler)) diff --git a/modules/reitit-schema/project.clj b/modules/reitit-schema/project.clj index 0663c72b..d107bada 100644 --- a/modules/reitit-schema/project.clj +++ b/modules/reitit-schema/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-schema "0.1.1" +(defproject metosin/reitit-schema "0.1.2-SNAPSHOT" :description "Reitit: Plumatic Schema coercion" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-spec/project.clj b/modules/reitit-spec/project.clj index f217ae61..9ad9b343 100644 --- a/modules/reitit-spec/project.clj +++ b/modules/reitit-spec/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-spec "0.1.1" +(defproject metosin/reitit-spec "0.1.2-SNAPSHOT" :description "Reitit: clojure.spec coercion" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-swagger-ui/project.clj b/modules/reitit-swagger-ui/project.clj index 5d530067..528791cd 100644 --- a/modules/reitit-swagger-ui/project.clj +++ b/modules/reitit-swagger-ui/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-swagger-ui "0.1.1" +(defproject metosin/reitit-swagger-ui "0.1.2-SNAPSHOT" :description "Reitit: Swagger-ui support" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc b/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc index 202556a8..137dbcb5 100644 --- a/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc +++ b/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc @@ -26,7 +26,7 @@ ;; with path and url set, swagger validator disabled (swagger-ui/create-swagger-ui-handler - {:path \"\" + {:path \"/\" :url \"/api/swagger.json\" :config {:validator-url nil})" ([] @@ -42,11 +42,11 @@ (update $ :root (fnil identity "swagger-ui")) (update $ :url (fnil identity "/swagger.json")) (update $ :config #(->> % (map mixed-case-key) (into {}))) - (assoc $ :paths {"conf.js" {:headers {"Content-Type" "application/javascript"} - :status 200 - :body (conf-js $)} - "config.json" {:headers {"Content-Type" "application/json"} - :status 200 - :body (config-json $)}}))] + (assoc $ :paths {"/conf.js" {:headers {"Content-Type" "application/javascript"} + :status 200 + :body (conf-js $)} + "/config.json" {:headers {"Content-Type" "application/json"} + :status 200 + :body (config-json $)}}))] (ring/routes (ring/create-resource-handler options)))))) diff --git a/modules/reitit-swagger/project.clj b/modules/reitit-swagger/project.clj index 0339c0cc..e301f223 100644 --- a/modules/reitit-swagger/project.clj +++ b/modules/reitit-swagger/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-swagger "0.1.1" +(defproject metosin/reitit-swagger "0.1.2-SNAPSHOT" :description "Reitit: Swagger-support" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit/project.clj b/modules/reitit/project.clj index 51aec6d4..6d1fffa5 100644 --- a/modules/reitit/project.clj +++ b/modules/reitit/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit "0.1.1" +(defproject metosin/reitit "0.1.2-SNAPSHOT" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/perf-test/clj/reitit/bide_perf_test.clj b/perf-test/clj/reitit/bide_perf_test.clj index 9e3aecf8..be26dcde 100644 --- a/perf-test/clj/reitit/bide_perf_test.clj +++ b/perf-test/clj/reitit/bide_perf_test.clj @@ -10,7 +10,8 @@ [io.pedestal.http.route.definition.table :as table] [io.pedestal.http.route.map-tree :as map-tree] [io.pedestal.http.route.router :as pedestal] - [io.pedestal.http.route :as route])) + [io.pedestal.http.route :as route] + [reitit.ring :as ring])) ;; ;; start repl with `lein perf repl` @@ -62,6 +63,26 @@ ["/auth/recovery/token/:token" :auth/recovery] ["/workspace/:project/:page" :workspace/page]])) +(def ring-app + (ring/ring-handler + (ring/router + [["/auth/login" {:get identity}] + ["/auth/recovery/token/:token" {:get identity}] + ["/workspace/:project/:page" {:get identity}]]))) + +(comment + + (ring-app {:request-method :get, :uri "/auth/login"}) + + ;; 213ns + ;; 204ns (remove if) + ;; 163ns (inline fast-assoc) + ;; 156ns (don't inline fast-assoc) + ;; 128ns (single method dispatch) + ;; 80ns --> (don't inject router & match) + (cc/quick-bench + (ring-app {:request-method :post, :uri "/auth/login"}))) + (defn routing-test1 [] (suite "static route") diff --git a/perf-test/clj/reitit/json_perf.cljc b/perf-test/clj/reitit/json_perf.cljc new file mode 100644 index 00000000..b9d81dfd --- /dev/null +++ b/perf-test/clj/reitit/json_perf.cljc @@ -0,0 +1,109 @@ +(ns reitit.json-perf + (:require [criterium.core :as cc] + [reitit.perf-utils :refer :all] + + ;; reitit + [reitit.ring :as ring] + [muuntaja.middleware :as mm] + + ;; bidi-yada + [yada.yada :as yada] + [bidi.ring :as bidi-ring] + [byte-streams :as bs] + + ;; defaults + [ring.middleware.defaults :as defaults] + [compojure.core :as compojure] + [clojure.string :as str])) + +;; +;; 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 +;; + +;; TODO: naive implementation +(defn- with-security-headers [response] + (update + response + :headers + (fn [headers] + (-> headers + (assoc "x-frame-options" "SAMEORIGIN") + (assoc "x-xss-protection" "1; mode=block") + (assoc "x-content-type-options" "nosniff"))))) + +(def security-middleware + {:name ::security + :wrap (fn [handler] + (fn [request] + (with-security-headers (handler request))))}) + +(def reitit-app + (ring/ring-handler + (ring/router + ["/api/ping" + {:get {:handler (fn [_] {:status 200, :body {:ping "pong"}})}}] + {:data {:middleware [mm/wrap-format + security-middleware]}}))) + +(def bidi-yada-app + (bidi-ring/make-handler + ["/api/ping" + (yada/resource + {:produces {:media-type "application/json"} + :methods {:get {:response (fn [_] {:ping "pong"})}}})])) + +(def defaults-app + (defaults/wrap-defaults + (mm/wrap-format + (compojure/GET "/api/ping" [] {:status 200, :body {:ping "pong"}})) + defaults/site-defaults)) + +(def request {:request-method :get, :uri "/api/ping"}) + +(comment + (defaults-app request) + @(bidi-yada-app request) + (reitit-app request)) + +(comment + (slurp (:body (defaults-app request))) + (slurp (:body (reitit-app request))) + (bs/to-string (:body @(bidi-yada-app request)))) + + +(defn expect! [body] + (assert (str/starts-with? body "{\"ping\":\"pong\"}"))) + +(defn perf-test [] + + ;; 176µs + (title "compojure + ring-defaults") + (let [f (fn [] (defaults-app request))] + (expect! (-> (f) :body slurp)) + (cc/quick-bench (f))) + + ;; 60µs + (title "bidi + yada") + (let [f (fn [] (bidi-yada-app request))] + (expect! (-> (f) deref :body bs/to-string)) + (cc/quick-bench (f))) + + ;; 5.0µs + (title "reitit-ring") + (let [f (fn [] (reitit-app request))] + (expect! (-> (f) :body slurp)) + (cc/quick-bench (f)))) + +(comment + (perf-test)) diff --git a/perf-test/clj/reitit/middleware_interceptor_perf.clj b/perf-test/clj/reitit/middleware_interceptor_perf.clj index d769ec61..1079533d 100644 --- a/perf-test/clj/reitit/middleware_interceptor_perf.clj +++ b/perf-test/clj/reitit/middleware_interceptor_perf.clj @@ -31,7 +31,7 @@ (defrecord RequestOrContext [values queue stack]) -(def +items+ 100) +(def +items+ 10) (defn expected! [x] (assert (= (range +items+) (:values x)))) @@ -47,18 +47,19 @@ map-request {} record-request (map->RequestOrContext map-request)] - ;; 10.8 µs + ;; 1000ns (title "middleware - map") (expected! (app map-request)) (cc/quick-bench (app map-request)) - ;; 4.7 µs + ;; 365ns (title "middleware - record") (expected! (app record-request)) (cc/quick-bench (app record-request)) + ;; 6900ns (title "middleware - dynamic") (expected! ((middleware/chain mw identity) record-request)) (cc/quick-bench @@ -110,21 +111,21 @@ {:enter (interceptor value)}) (range +items+))) ctx (io.pedestal.interceptor.chain/enqueue nil is)] - ;; 78 µs + ;; 8400ns (title "pedestal") (cc/quick-bench (io.pedestal.interceptor.chain/execute ctx)))) -(defn pedestal-tuned-chain-text [] - (let [is (map io.pedestal.interceptor/interceptor - (map (fn [value] - {:enter (interceptor value)}) (range +items+))) - ctx (reitit.chain/map->Context (reitit.chain/enqueue nil is))] +#_(defn pedestal-tuned-chain-text [] + (let [is (map io.pedestal.interceptor/interceptor + (map (fn [value] + {:enter (interceptor value)}) (range +items+))) + ctx (reitit.chain/map->Context (reitit.chain/enqueue nil is))] - ;; 67 µs - (title "pedestal - tuned") - (cc/quick-bench - (reitit.chain/execute ctx)))) + ;; 67 µs + (title "pedestal - tuned") + (cc/quick-bench + (reitit.chain/execute ctx)))) ;; ;; Naive chain @@ -140,17 +141,17 @@ (defn interceptor-test [] (let [interceptors (map (fn [value] [interceptor value]) (range +items+)) - app (executor-reduce (interceptor/chain interceptors identity)) + app (executor-reduce (interceptor/chain interceptors identity {})) map-request {} record-request (map->RequestOrContext map-request)] - ;; 13.5 µs (Map) + ;; 1900ns (title "interceptors - map") (expected! (app map-request)) (cc/quick-bench (app map-request)) - ;; 7.2 µs (Record) + ;; 1300ns (title "interceptors - record") (expected! (app record-request)) (cc/quick-bench @@ -212,24 +213,24 @@ (defn interceptor-chain-test [] (let [interceptors (map (fn [value] [interceptor value]) (range +items+)) - app-reduce (executor-reduce (interceptor/chain interceptors identity)) - app-queue (executor-queue (interceptor/chain interceptors identity)) - app-ctx-queue (executor-ctx-queue (interceptor/chain interceptors identity)) + app-reduce (executor-reduce (interceptor/chain interceptors identity {})) + app-queue (executor-queue (interceptor/chain interceptors identity {})) + app-ctx-queue (executor-ctx-queue (interceptor/chain interceptors identity {})) request {}] - ;; 14.2 µs + ;; 2000ns (title "interceptors - reduce") (expected! (app-reduce request)) (cc/quick-bench (app-reduce request)) - ;; 19.4 µs + ;; 2500ns (title "interceptors - queue") (expected! (app-queue request)) (cc/quick-bench (app-queue request)) - ;; 30.9 µs + ;; 3200ns (title "interceptors - ctx-queue") (expected! (app-ctx-queue request)) (cc/quick-bench diff --git a/project.clj b/project.clj index 7a682cc8..bbe85707 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-parent "0.1.1" +(defproject metosin/reitit-parent "0.1.2-SNAPSHOT" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" @@ -9,13 +9,13 @@ :source-uri "https://github.com/metosin/reitit/{version}/{filepath}#L{line}" :metadata {:doc/format :markdown}} - :managed-dependencies [[metosin/reitit "0.1.1"] - [metosin/reitit-core "0.1.1"] - [metosin/reitit-ring "0.1.1"] - [metosin/reitit-spec "0.1.1"] - [metosin/reitit-schema "0.1.1"] - [metosin/reitit-swagger "0.1.1"] - [metosin/reitit-swagger-ui "0.1.1"] + :managed-dependencies [[metosin/reitit "0.1.2-SNAPSHOT"] + [metosin/reitit-core "0.1.2-SNAPSHOT"] + [metosin/reitit-ring "0.1.2-SNAPSHOT"] + [metosin/reitit-spec "0.1.2-SNAPSHOT"] + [metosin/reitit-schema "0.1.2-SNAPSHOT"] + [metosin/reitit-swagger "0.1.2-SNAPSHOT"] + [metosin/reitit-swagger-ui "0.1.2-SNAPSHOT"] [meta-merge "1.0.0"] [ring/ring-core "1.6.3"] @@ -53,7 +53,7 @@ [ring "1.6.3"] [ikitommi/immutant-web "3.0.0-alpha1"] - [metosin/muuntaja "0.5.0"] + [metosin/muuntaja "0.6.0-SNAPSHOT"] [metosin/ring-swagger-ui "2.2.10"] [metosin/jsonista "0.2.1"] @@ -70,6 +70,8 @@ [ikitommi/immutant-web "3.0.0-alpha1"] [io.pedestal/pedestal.route "0.5.3"] [org.clojure/core.async "0.4.474"] + [yada "1.2.13"] + [ring/ring-defaults "0.3.1"] [ataraxy "0.4.0"] [bidi "2.1.3"]]} :analyze {:jvm-opts ^:replace ["-server" diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index e8144e75..f8233888 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -108,6 +108,12 @@ r/segment-router :segment-router r/mixed-router :mixed-router)) + (testing "nil routes are stripped" + (is (= [] (r/routes (r/router nil)))) + (is (= [] (r/routes (r/router [nil ["/ping"]])))) + (is (= [] (r/routes (r/router [nil [nil] [[nil nil nil]]])))) + (is (= [] (r/routes (r/router ["/ping" [nil "/pong"]]))))) + (testing "route coercion & compilation" (testing "custom compile" diff --git a/test/cljc/reitit/ring_test.cljc b/test/cljc/reitit/ring_test.cljc index 27a5ad83..5fb6d3d1 100644 --- a/test/cljc/reitit/ring_test.cljc +++ b/test/cljc/reitit/ring_test.cljc @@ -267,56 +267,154 @@ #?(: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-resource-handler)]] - {:conflicts (constantly nil)}) - (ring/create-default-handler))] + (let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}}) + request (fn [uri] {:uri uri, :request-method :get})] + (testing "inside a router" - ["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-resource-handler {:path "/"}) - (ring/create-default-handler)))]] - prefix ["" "/" "/files" "/files/"] - :let [request (fn [uri] {:uri (str prefix uri), :request-method :get})]] + (testing "from root" + (let [app (ring/ring-handler + (ring/router + ["/*" (ring/create-resource-handler)]) + (ring/create-default-handler))] + (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 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 "index-files" - (let [response (app (request "/docs"))] - (is (= "text/html" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "

hello

\n" (slurp (:body response))))) - (let [response (app (request "/docs/"))] - (is (= "text/html" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "

hello

\n" (slurp (:body response)))))) + (testing "not found" + (let [response (app (request "/not-found"))] + (is (= 404 (:status 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 "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 (ring/ring-handler + (ring/router + ["/files/*" (ring/create-resource-handler)]) + (ring/create-default-handler)) + 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 (ring/ring-handler + (ring/router []) + (ring/routes + (ring/create-resource-handler {:path "/"}) + (ring/create-default-handler)))] + (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 (ring/ring-handler + (ring/router []) + (ring/routes + (ring/create-resource-handler {:path "/files"}) + (ring/create-default-handler))) + 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))))))))))))) diff --git a/test/cljc/reitit/spec_test.cljc b/test/cljc/reitit/spec_test.cljc index 62012cd2..880328e5 100644 --- a/test/cljc/reitit/spec_test.cljc +++ b/test/cljc/reitit/spec_test.cljc @@ -1,14 +1,20 @@ (ns reitit.spec-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [deftest testing is are use-fixtures]] [#?(:clj clojure.spec.test.alpha :cljs cljs.spec.test.alpha) :as stest] [clojure.spec.alpha :as s] [reitit.core :as r] - [reitit.spec :as rs] - [expound.alpha :as e]) + [reitit.spec :as rs]) #?(:clj (:import (clojure.lang ExceptionInfo)))) -(stest/instrument) +(defn instrument-all [f] + (try + (stest/instrument) + (f) + (finally + (stest/unstrument)))) + +(use-fixtures :each instrument-all) (deftest router-spec-test @@ -40,9 +46,9 @@ ;; path [:invalid {}] - ;; vector data - ["/api" [] - ["/ipa"]]))) + ;; nested path + ["/api" + [:ipa]]))) (testing "routes conform to spec (can't spec protocol functions)" (is (s/valid? ::rs/routes (r/routes (r/router ["/ping"])))))