diff --git a/CHANGELOG.md b/CHANGELOG.md index d77091f8..69ef1f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,29 @@ We use [Break Versioning][breakver]. The version numbers follow a `. data` to expand route arg to route data (default `reitit.core/expand`) -| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` -| `:compile` | Function of `route opts => result` to compile a route handler -| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects -| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes -| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) -| `:router` | Function of `routes opts => router` to override the actual router implementation +| key | description +|---------------|------------- +| `:path` | Base-path for routes +| `:routes` | Initial resolved routes (default `[]`) +| `:data` | Initial route data (default `{}`) +| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this +| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) +| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) +| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` +| `:meta-merge` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging +| `:compile` | Function of `route opts => result` to compile a route handler +| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects +| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes +| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) +| `:router` | Function of `routes opts => router` to override the actual router implementation diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index 54eca320..719a0611 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -23,6 +23,7 @@ The following route data keys contribute to the generated swagger specification: | :tags | optional set of string or keyword tags for an endpoint api docs | :summary | optional short string summary of an endpoint | :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ +| :operationId | optional string specifying the unique ID of an Operation Coercion keys also contribute to the docs: diff --git a/examples/pedestal-malli-swagger/.gitignore b/examples/pedestal-malli-swagger/.gitignore new file mode 100644 index 00000000..708f60f6 --- /dev/null +++ b/examples/pedestal-malli-swagger/.gitignore @@ -0,0 +1,11 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.hgignore +.hg/ \ No newline at end of file diff --git a/examples/pedestal-malli-swagger/project.clj b/examples/pedestal-malli-swagger/project.clj new file mode 100644 index 00000000..694d3beb --- /dev/null +++ b/examples/pedestal-malli-swagger/project.clj @@ -0,0 +1,9 @@ +(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT" + :description "Reitit-http with pedestal" + :dependencies [[org.clojure/clojure "1.10.0"] + [io.pedestal/pedestal.service "0.5.5"] + [io.pedestal/pedestal.jetty "0.5.5"] + [metosin/reitit-malli "0.5.18"] + [metosin/reitit-pedestal "0.5.18"] + [metosin/reitit "0.5.18"]] + :repl-options {:init-ns server}) diff --git a/examples/pedestal-malli-swagger/src/server.clj b/examples/pedestal-malli-swagger/src/server.clj new file mode 100644 index 00000000..72c93967 --- /dev/null +++ b/examples/pedestal-malli-swagger/src/server.clj @@ -0,0 +1,164 @@ +(ns example.server + (:require [clojure.java.io :as io] + [io.pedestal.http.route] + [reitit.interceptor] + [reitit.dev.pretty :as pretty] + [reitit.coercion.malli] + [io.pedestal.http] + [reitit.ring] + [reitit.ring.malli] + [reitit.http] + [reitit.pedestal] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [reitit.http.coercion :as coercion] + [reitit.http.interceptors.parameters :as parameters] + [reitit.http.interceptors.muuntaja :as muuntaja] + [reitit.http.interceptors.multipart :as multipart] + [muuntaja.core] + [malli.util :as mu])) + +(defn reitit-routes + [_config] + [["/swagger.json" {:get {:no-doc true + :swagger {:info {:title "my-api" + :description "with [malli](https://github.com/metosin/malli) and reitit-ring"} + :tags [{:name "files", + :description "file api"} + {:name "math", + :description "math api"}]} + :handler (swagger/create-swagger-handler)}}] + ["/files" {:swagger {:tags ["files"]}} + ["/upload" + {:post {:summary "upload a file" + :parameters {:multipart [:map [:file reitit.ring.malli/temp-file-part]]} + :responses {200 {:body [:map + [:name string?] + [:size int?]]}} + :handler (fn [{{{{:keys [filename + size]} :file} + :multipart} + :parameters}] + {:status 200 + :body {:name filename + :size size}})}}] + ["/download" {:get {:summary "downloads a file" + :swagger {:produces ["image/png"]} + :handler (fn [_] + {:status 200 + :headers {"Content-Type" "image/png"} + :body (-> "reitit.png" + (io/resource) + (io/input-stream))})}}]] + ["/math" {:swagger {:tags ["math"]}} + ["/plus" + {:get {:summary "plus with malli query parameters" + :parameters {:query [:map + [:x + {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42} + int?] + [:y int?]]} + :responses {200 {:body [:map [:total int?]]}} + :handler (fn [{{{:keys [x + y]} + :query} + :parameters}] + {:status 200 + :body {:total (+ x y)}})} + :post {:summary "plus with malli body parameters" + :parameters {:body [:map + [:x + {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42} + int?] + [:y int?]]} + :responses {200 {:body [:map [:total int?]]}} + :handler (fn [{{{:keys [x + y]} + :body} + :parameters}] + {:status 200 + :body {:total (+ x y)}})}}]]]) + +(defn reitit-ring-routes + [_config] + [(swagger-ui/create-swagger-ui-handler + {:path "/" + :config {:validatorUrl nil + :operationsSorter "alpha"}}) + (reitit.ring/create-resource-handler) + (reitit.ring/create-default-handler)]) + + +(defn reitit-router-config + [_config] + {:exception pretty/exception + :data {:coercion (reitit.coercion.malli/create + {:error-keys #{:coercion + :in + :schema + :value + :errors + :humanized} + :compile mu/closed-schema + :strip-extra-keys true + :default-values true + :options nil}) + :muuntaja muuntaja.core/instance + :interceptors [swagger/swagger-feature + (parameters/parameters-interceptor) + (muuntaja/format-negotiate-interceptor) + (muuntaja/format-response-interceptor) + (muuntaja/format-request-interceptor) + (coercion/coerce-response-interceptor) + (coercion/coerce-request-interceptor) + (multipart/multipart-interceptor)]}}) + +(def config + {:env :dev + :io.pedestal.http/routes [] + :io.pedestal.http/type :jetty + :io.pedestal.http/port 3000 + :io.pedestal.http/join? false + :io.pedestal.http/secure-headers {:content-security-policy-settings + {:default-src "'self'" + :style-src "'self' 'unsafe-inline'" + :script-src "'self' 'unsafe-inline'"}} + ::reitit-routes reitit-routes + ::reitit-ring-routes reitit-ring-routes + ::reitit-router-config reitit-router-config}) + +(defn reitit-http-router + [{::keys [reitit-routes + reitit-ring-routes + reitit-router-config] + :as config}] + (reitit.pedestal/routing-interceptor + (reitit.http/router + (reitit-routes config) + (reitit-router-config config)) + (->> config + reitit-ring-routes + (apply reitit.ring/routes)))) + +(defonce server (atom nil)) + +(defn start + [server + config] + (when @server + (io.pedestal.http/stop @server) + (println "server stopped")) + (-> config + io.pedestal.http/default-interceptors + (reitit.pedestal/replace-last-interceptor (reitit-http-router config)) + io.pedestal.http/dev-interceptors + io.pedestal.http/create-server + io.pedestal.http/start + (->> (reset! server))) + (println "server running in port 3000")) + +#_(start server config) \ No newline at end of file diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index a8b0d9c6..442409da 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -41,36 +41,44 @@ :header (->ParameterCoercion :headers :string true true) :path (->ParameterCoercion :path-params :string true true)}) -(defn ^:no-doc request-coercion-failed! [result coercion value in request] +(defn ^:no-doc request-coercion-failed! [result coercion value in request serialize-failed-result] (throw (ex-info - (str "Request coercion failed: " (pr-str result)) - (merge - (into {} result) - {:type ::request-coercion - :coercion coercion - :value value - :in [:request in] - :request request})))) + (if serialize-failed-result + (str "Request coercion failed: " (pr-str result)) + "Request coercion failed") + (-> {} + transient + (as-> $ (reduce conj! $ result)) + (assoc! :type ::request-coercion) + (assoc! :coercion coercion) + (assoc! :value value) + (assoc! :in [:request in]) + (assoc! :request request) + persistent!)))) -(defn ^:no-doc response-coercion-failed! [result coercion value request response] +(defn ^:no-doc response-coercion-failed! [result coercion value request response serialize-failed-result] (throw (ex-info - (str "Response coercion failed: " (pr-str result)) - (merge - (into {} result) - {:type ::response-coercion - :coercion coercion - :value value - :in [:response :body] - :request request - :response response})))) + (if serialize-failed-result + (str "Response coercion failed: " (pr-str result)) + "Response coercion failed") + (-> {} + transient + (as-> $ (reduce conj! $ result)) + (assoc! :type ::response-coercion) + (assoc! :coercion coercion) + (assoc! :value value) + (assoc! :in [:response :body]) + (assoc! :request request) + (assoc! :response response) + persistent!)))) (defn extract-request-format-default [request] (-> request :muuntaja/request :format)) ;; TODO: support faster key walking, walk/keywordize-keys is quite slow... -(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion] +(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result] :or {extract-request-format extract-request-format-default parameter-coercion default-parameter-coercion}}] (if coercion @@ -83,13 +91,13 @@ format (extract-request-format request) result (coercer value format)] (if (error? result) - (request-coercion-failed! result coercion value in request) + (request-coercion-failed! result coercion value in request serialize-failed-result) result)))))))) (defn extract-response-format-default [request _] (-> request :muuntaja/response :format)) -(defn response-coercer [coercion body {:keys [extract-response-format] +(defn response-coercer [coercion body {:keys [extract-response-format serialize-failed-result] :or {extract-response-format extract-response-format-default}}] (if coercion (if-let [coercer (-response-coercer coercion body)] @@ -98,7 +106,7 @@ value (:body response) result (coercer value format)] (if (error? result) - (response-coercion-failed! result coercion value request response) + (response-coercion-failed! result coercion value request response serialize-failed-result) result)))))) (defn encode-error [data] diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 03c55e72..8c6d1cbb 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -60,17 +60,20 @@ (defn map-data [f routes] (mapv (fn [[p ds]] [p (f p ds)]) routes)) -(defn merge-data [p x] +(defn meta-merge [left right opts] + ((or (:meta-merge opts) mm/meta-merge) left right)) + +(defn merge-data [opts p x] (reduce (fn [acc [k v]] (try - (mm/meta-merge acc {k v}) + (meta-merge acc {k v} opts) (catch #?(:clj Exception, :cljs js/Error) e (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) {} x)) (defn resolve-routes [raw-routes {:keys [coerce] :as opts}] - (cond->> (->> (walk raw-routes opts) (map-data merge-data)) + (cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2))) coerce (into [] (keep #(coerce % opts))))) (defn path-conflicting-routes [routes opts] @@ -249,6 +252,10 @@ (->> params (map (fn [[k v]] (if (or (sequential? v) (set? v)) - (str/join "&" (map query-parameter (repeat k) v)) + (if (seq v) + (str/join "&" (map query-parameter (repeat k) v)) + ;; Empty seq results in single & character in the query string. + ;; Handle as empty string to behave similarly as when the value is nil. + (query-parameter k "")) (query-parameter k v)))) (str/join "&"))) diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index 8d90c2e4..22d3584b 100644 --- a/modules/reitit-core/src/reitit/interceptor.cljc +++ b/modules/reitit-core/src/reitit/interceptor.cljc @@ -1,6 +1,5 @@ (ns reitit.interceptor (:require [clojure.pprint :as pprint] - [meta-merge.core :refer [meta-merge]] [reitit.core :as r] [reitit.exception :as exception] [reitit.impl :as impl])) @@ -156,13 +155,13 @@ ([data] (router data nil)) ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + (let [opts (impl/meta-merge {:compile compile-result} opts opts)] (r/router data opts)))) (defn interceptor-handler [router] (with-meta - (fn [path] - (some->> (r/match-by-path router path) - :result - :interceptors)) - {::router router})) + (fn [path] + (some->> (r/match-by-path router path) + :result + :interceptors)) + {::router router})) diff --git a/modules/reitit-core/src/reitit/middleware.cljc b/modules/reitit-core/src/reitit/middleware.cljc index 49381b68..3f1be365 100644 --- a/modules/reitit-core/src/reitit/middleware.cljc +++ b/modules/reitit-core/src/reitit/middleware.cljc @@ -1,6 +1,5 @@ (ns reitit.middleware (:require [clojure.pprint :as pprint] - [meta-merge.core :refer [meta-merge]] [reitit.core :as r] [reitit.exception :as exception] [reitit.impl :as impl])) @@ -139,14 +138,14 @@ ([data] (router data nil)) ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + (let [opts (impl/meta-merge {:compile compile-result} opts opts)] (r/router data opts)))) (defn middleware-handler [router] (with-meta - (fn [path] - (some->> path - (r/match-by-path router) - :result - :handler)) - {::router router})) + (fn [path] + (some->> path + (r/match-by-path router) + :result + :handler)) + {::router router})) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index 6715eae3..62595b2b 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -18,7 +18,7 @@ (s/nilable (s/cat :path ::path :arg (s/? ::arg) - :childs (s/* (s/and (s/nilable ::raw-routes)))))) + :childs (s/* (s/nilable ::raw-routes))))) (s/def ::raw-routes (s/or :route ::raw-route diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 22c6356b..061dbeab 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -176,10 +176,10 @@ (fn [_ [p n]] (if-let [cp (common-prefix p path)] (if (= cp p) - ;; insert into child node + ;; insert into child node (let [n' (-insert n (conj ps (subs path (count p))) fp params data)] (reduced (assoc-in node [:children p] n'))) - ;; split child node + ;; split child node (let [rp (subs p (count cp)) rp' (subs path (count cp)) n' (-insert (-node {}) ps fp params data) @@ -189,7 +189,7 @@ (dissoc p) (assoc cp n''))))))))) nil (:children node)) - ;; new child node + ;; new child node (assoc-in node [:children path] (-insert (-node {}) ps fp params data))))] (if-let [child (get-in node' [:children ""])] ;; optimize by removing empty paths @@ -385,62 +385,62 @@ ;; (comment - (-> - [["/v2/whoami" 1] - ["/v2/users/:user-id/datasets" 2] - ["/v2/public/projects/:project-id/datasets" 3] - ["/v1/public/topics/:topic" 4] - ["/v1/users/:user-id/orgs/:org-id" 5] - ["/v1/search/topics/:term" 6] - ["/v1/users/:user-id/invitations" 7] - ["/v1/users/:user-id/topics" 9] - ["/v1/users/:user-id/bookmarks/followers" 10] - ["/v2/datasets/:dataset-id" 11] - ["/v1/orgs/:org-id/usage-stats" 12] - ["/v1/orgs/:org-id/devices/:client-id" 13] - ["/v1/messages/user/:user-id" 14] - ["/v1/users/:user-id/devices" 15] - ["/v1/public/users/:user-id" 16] - ["/v1/orgs/:org-id/errors" 17] - ["/v1/public/orgs/:org-id" 18] - ["/v1/orgs/:org-id/invitations" 19] - ["/v1/users/:user-id/device-errors" 22] - ["/v2/login" 23] - ["/v1/users/:user-id/usage-stats" 24] - ["/v2/users/:user-id/devices" 25] - ["/v1/users/:user-id/claim-device/:client-id" 26] - ["/v2/public/projects/:project-id" 27] - ["/v2/public/datasets/:dataset-id" 28] - ["/v2/users/:user-id/topics/bulk" 29] - ["/v1/messages/device/:client-id" 30] - ["/v1/users/:user-id/owned-orgs" 31] - ["/v1/topics/:topic" 32] - ["/v1/users/:user-id/bookmark/:topic" 33] - ["/v1/orgs/:org-id/members/:user-id" 34] - ["/v1/users/:user-id/devices/:client-id" 35] - ["/v1/users/:user-id" 36] - ["/v1/orgs/:org-id/devices" 37] - ["/v1/orgs/:org-id/members" 38] - ["/v2/orgs/:org-id/topics" 40] - ["/v1/whoami" 41] - ["/v1/orgs/:org-id" 42] - ["/v1/users/:user-id/api-key" 43] - ["/v2/schemas" 44] - ["/v2/users/:user-id/topics" 45] - ["/v1/orgs/:org-id/confirm-membership/:token" 46] - ["/v2/topics/:topic" 47] - ["/v1/messages/topic/:topic" 48] - ["/v1/users/:user-id/devices/:client-id/reset-password" 49] - ["/v2/topics" 50] - ["/v1/login" 51] - ["/v1/users/:user-id/orgs" 52] - ["/v2/public/messages/dataset/:dataset-id" 53] - ["/v1/topics" 54] - ["/v1/orgs" 55] - ["/v1/users/:user-id/bookmarks" 56] - ["/v1/orgs/:org-id/topics" 57] - ["/command1 {arg1} {arg2}" ::cmd1] - ["/command2 {arg1} {arg2} {arg3}" ::cmd2]] - (insert) - (compile) - (pretty))) + (-> + [["/v2/whoami" 1] + ["/v2/users/:user-id/datasets" 2] + ["/v2/public/projects/:project-id/datasets" 3] + ["/v1/public/topics/:topic" 4] + ["/v1/users/:user-id/orgs/:org-id" 5] + ["/v1/search/topics/:term" 6] + ["/v1/users/:user-id/invitations" 7] + ["/v1/users/:user-id/topics" 9] + ["/v1/users/:user-id/bookmarks/followers" 10] + ["/v2/datasets/:dataset-id" 11] + ["/v1/orgs/:org-id/usage-stats" 12] + ["/v1/orgs/:org-id/devices/:client-id" 13] + ["/v1/messages/user/:user-id" 14] + ["/v1/users/:user-id/devices" 15] + ["/v1/public/users/:user-id" 16] + ["/v1/orgs/:org-id/errors" 17] + ["/v1/public/orgs/:org-id" 18] + ["/v1/orgs/:org-id/invitations" 19] + ["/v1/users/:user-id/device-errors" 22] + ["/v2/login" 23] + ["/v1/users/:user-id/usage-stats" 24] + ["/v2/users/:user-id/devices" 25] + ["/v1/users/:user-id/claim-device/:client-id" 26] + ["/v2/public/projects/:project-id" 27] + ["/v2/public/datasets/:dataset-id" 28] + ["/v2/users/:user-id/topics/bulk" 29] + ["/v1/messages/device/:client-id" 30] + ["/v1/users/:user-id/owned-orgs" 31] + ["/v1/topics/:topic" 32] + ["/v1/users/:user-id/bookmark/:topic" 33] + ["/v1/orgs/:org-id/members/:user-id" 34] + ["/v1/users/:user-id/devices/:client-id" 35] + ["/v1/users/:user-id" 36] + ["/v1/orgs/:org-id/devices" 37] + ["/v1/orgs/:org-id/members" 38] + ["/v2/orgs/:org-id/topics" 40] + ["/v1/whoami" 41] + ["/v1/orgs/:org-id" 42] + ["/v1/users/:user-id/api-key" 43] + ["/v2/schemas" 44] + ["/v2/users/:user-id/topics" 45] + ["/v1/orgs/:org-id/confirm-membership/:token" 46] + ["/v2/topics/:topic" 47] + ["/v1/messages/topic/:topic" 48] + ["/v1/users/:user-id/devices/:client-id/reset-password" 49] + ["/v2/topics" 50] + ["/v1/login" 51] + ["/v1/users/:user-id/orgs" 52] + ["/v2/public/messages/dataset/:dataset-id" 53] + ["/v1/topics" 54] + ["/v1/orgs" 55] + ["/v1/users/:user-id/bookmarks" 56] + ["/v1/orgs/:org-id/topics" 57] + ["/command1 {arg1} {arg2}" ::cmd1] + ["/command2 {arg1} {arg2} {arg3}" ::cmd2]] + (insert) + (compile) + (pretty))) diff --git a/modules/reitit-dev/src/reitit/dev/pretty.cljc b/modules/reitit-dev/src/reitit/dev/pretty.cljc index 158d09e0..78e683a3 100644 --- a/modules/reitit-dev/src/reitit/dev/pretty.cljc +++ b/modules/reitit-dev/src/reitit/dev/pretty.cljc @@ -9,8 +9,7 @@ [fipp.engine] [fipp.visit] [reitit.exception :as exception] - [spell-spec.expound] ;; expound -)) + [spell-spec.expound])) ;; expound ;; ;; colors @@ -46,17 +45,17 @@ :error 196}) (comment - (defn- -color [color & text] - (str "\033[38;5;" (colors color color) "m" (apply str text) "\u001B[0m")) + (defn- -color [color & text] + (str "\033[38;5;" (colors color color) "m" (apply str text) "\u001B[0m")) - (doseq [c (range 0 255)] - (println (-color c "kikka") "->" c)) + (doseq [c (range 0 255)] + (println (-color c "kikka") "->" c)) - (doseq [[n c] colors] - (println (-color c "kikka") "->" c n)) + (doseq [[n c] colors] + (println (-color c "kikka") "->" c n)) - (doseq [[k v] expound.ansi/sgr-code] - (println (expound.ansi/sgr "kikka" k) "->" k))) + (doseq [[k v] expound.ansi/sgr-code] + (println (expound.ansi/sgr "kikka" k) "->" k))) (defn- -start [x] (str "\033[38;5;" x "m")) (defn- -end [] "\u001B[0m") @@ -220,10 +219,10 @@ (defn exception [e] (let [data (-> e ex-data :data) message (format-exception (-> e ex-data :type) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) data) - source #?(:clj (->> e Throwable->map :trace - (drop-while #(not= (name (first %)) "reitit.core$router")) - (drop-while #(= (name (first %)) "reitit.core$router")) - next first source-str) + source #?(:clj (->> e Throwable->map :trace + (drop-while #(not= (name (first %)) "reitit.core$router")) + (drop-while #(= (name (first %)) "reitit.core$router")) + next first source-str) :cljs "unknown")] (ex-info (exception-str message source (printer)) (assoc (or data {}) ::exception/cause e)))) diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index c85817f8..bf0f3ae5 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -1,7 +1,7 @@ (ns reitit.http - (:require [meta-merge.core :refer [meta-merge]] - [reitit.core :as r] + (:require [reitit.core :as r] [reitit.exception :as ex] + [reitit.impl :as impl] [reitit.interceptor :as interceptor] [reitit.ring :as ring])) @@ -38,7 +38,7 @@ (->methods true top) (reduce-kv (fn [acc method data] - (let [data (meta-merge top data)] + (let [data (impl/meta-merge top data opts)] (assoc acc method (->endpoint path data method method)))) (->methods (:handler top) data) childs)))) @@ -138,35 +138,35 @@ enrich-request (ring/create-enrich-request inject-match? inject-router?) enrich-default-request (ring/create-enrich-default-request inject-router?)] (with-meta - (fn - ([request] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - endpoint (-> match :result method) - interceptors (or (:queue endpoint) (:interceptors endpoint)) - request (enrich-request request path-params match router)] - (or (interceptor/execute executor interceptors request) - (interceptor/execute executor default-queue request))) - (interceptor/execute executor default-queue (enrich-default-request request router)))) - ([request respond raise] - (let [default #(interceptor/execute executor default-queue % respond raise)] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - endpoint (-> match :result method) - interceptors (or (:queue endpoint) (:interceptors endpoint)) - request (enrich-request request path-params match router) - respond' (fn [response] - (if response - (respond response) - (default request)))] - (if interceptors - (interceptor/execute executor interceptors request respond' raise) - (default request))) - (default (enrich-default-request request router)))) - nil)) - {::r/router router})))) + (fn + ([request] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + endpoint (-> match :result method) + interceptors (or (:queue endpoint) (:interceptors endpoint)) + request (enrich-request request path-params match router)] + (or (interceptor/execute executor interceptors request) + (interceptor/execute executor default-queue request))) + (interceptor/execute executor default-queue (enrich-default-request request router)))) + ([request respond raise] + (let [default #(interceptor/execute executor default-queue % respond raise)] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + endpoint (-> match :result method) + interceptors (or (:queue endpoint) (:interceptors endpoint)) + request (enrich-request request path-params match router) + respond' (fn [response] + (if response + (respond response) + (default request)))] + (if interceptors + (interceptor/execute executor interceptors request respond' raise) + (default request))) + (default (enrich-default-request request router)))) + nil)) + {::r/router router})))) (defn get-router [handler] (-> handler meta ::r/router)) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj index 896f99fc..54173d57 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj @@ -20,7 +20,9 @@ (defn- polish [ctx] (-> ctx - (dissoc ::original ::previous :stack :queue) + (dissoc ::original ::previous :stack :queue + :io.pedestal.interceptor.chain/stack + :io.pedestal.interceptor.chain/queue) (update :request dissoc ::r/match ::r/router))) (defn- handle [name stage] diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 949da00f..5d4a863a 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -115,7 +115,7 @@ :response {:default default-transformer-provider :formats {"application/json" json-transformer-provider}}} ;; set of keys to include in error messages - :error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed} + :error-keys #{:type :coercion :in #_:schema :value #_:errors :humanized #_:transformed} ;; support lite syntax? :lite true ;; schema identity function (default: close all map schemas) @@ -211,7 +211,7 @@ show? (fn [key] (contains? error-keys key)) transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers) compile (if lite (fn [schema options] (compile (binding [l/*options* options] (l/schema schema)) options)) - compile)] + compile)] ^{:type ::coercion/coercion} (reify coercion/Coercion (-get-name [_] :malli) diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 4aceaaf1..9bb6d8d6 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -1,8 +1,7 @@ (ns reitit.ring (:require [clojure.string :as str] - [meta-merge.core :refer [meta-merge]] #?@(:clj [[ring.util.mime-type :as mime-type] - [ring.util.response :as response]]) + [ring.util.response :as response]]) [reitit.core :as r] [reitit.exception :as ex] [reitit.impl :as impl] @@ -50,21 +49,21 @@ (->methods true top) (reduce-kv (fn [acc method data] - (let [data (meta-merge top data)] + (let [data (impl/meta-merge top data opts)] (assoc acc method (->endpoint path data method method)))) (->methods (:handler top) data) childs)))) (def default-options-handler - (let [handle (fn [request] - (let [methods (->> request get-match :result (keep (fn [[k v]] (if v k)))) - allow (->> methods (map (comp str/upper-case name)) (str/join ","))] - {:status 200, :body "", :headers {"Allow" allow}}))] + (let [handler (fn [request] + (let [methods (->> request get-match :result (keep (fn [[k v]] (if v k)))) + allow (->> methods (map (comp str/upper-case name)) (str/join ","))] + {:status 200, :body "", :headers {"Allow" allow}}))] (fn ([request] - (handle request)) + (handler request)) ([request respond _] - (respond (handle request)))))) + (respond (handler request)))))) (def default-options-endpoint {:no-doc true @@ -133,10 +132,10 @@ " ([] (redirect-trailing-slash-handler {:method :both})) ([{:keys [method]}] - (letfn [(maybe-redirect [request path] + (letfn [(maybe-redirect [{:keys [query-string] :as request} path] (if (and (seq path) (r/match-by-path (::r/router request) path)) {:status (if (= (:request-method request) :get) 301 308) - :headers {"Location" path} + :headers {"Location" (if query-string (str path "?" query-string) path)} :body ""})) (redirect-handler [request] (let [uri (:uri request)] @@ -317,28 +316,28 @@ enrich-request (create-enrich-request inject-match? inject-router?) enrich-default-request (create-enrich-default-request inject-router?)] (with-meta - (wrap - (fn - ([request] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - result (:result match) - handler (-> result method :handler (or default-handler)) - request (enrich-request request path-params match router)] - (or (handler request) (default-handler request))) - (default-handler (enrich-default-request request router)))) - ([request respond raise] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - result (:result match) - handler (-> result method :handler (or default-handler)) - request (enrich-request request path-params match router)] - ((routes handler default-handler) request respond raise)) - (default-handler (enrich-default-request request router) respond raise)) - nil))) - {::r/router router})))) + (wrap + (fn + ([request] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + handler (-> result method :handler (or default-handler)) + request (enrich-request request path-params match router)] + (or (handler request) (default-handler request))) + (default-handler (enrich-default-request request router)))) + ([request respond raise] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + handler (-> result method :handler (or default-handler)) + request (enrich-request request path-params match router)] + ((routes handler default-handler) request respond raise)) + (default-handler (enrich-default-request request router) respond raise)) + nil))) + {::r/router router})))) (defn get-router [handler] (-> handler meta ::r/router)) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index b647b846..c9f22a0f 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -48,7 +48,7 @@ (-get-name [_] :schema) (-get-options [_] opts) (-get-apidocs [this specification {:keys [parameters responses]}] - ;; TODO: this looks identical to spec, refactor when schema is done. + ;; TODO: this looks identical to spec, refactor when schema is done. (case specification :swagger (swagger/swagger-spec (merge diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index 2c71a6d3..3c3403cb 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -10,11 +10,13 @@ (s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{}))) (s/def ::no-doc boolean?) (s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{})) +(s/def ::operationId string?) (s/def ::summary string?) (s/def ::description string?) +(s/def ::operationId string?) (s/def ::swagger (s/keys :opt-un [::id])) -(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description])) +(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description ::operationId])) (def swagger-feature "Feature for handling swagger-documentation for routes. @@ -52,6 +54,7 @@ [\"/plus\" {:get {:swagger {:tags \"math\"} + :operationId \"addTwoNumbers\" :summary \"adds numbers together\" :description \"takes `x` and `y` query-params and adds them together\" :parameters {:query {:x int?, :y int?}} @@ -75,7 +78,7 @@ (let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger) ids (trie/into-set id) strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions) - strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description) + strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description :operationId) swagger (->> (strip-endpoint-keys swagger) (merge {:swagger "2.0" :x-id ids})) @@ -93,7 +96,7 @@ (apply meta-merge (keep (comp :swagger :data) interceptors)) (if coercion (coercion/get-apidocs coercion :swagger data)) - (select-keys data [:tags :summary :description]) + (select-keys data [:tags :summary :description :operationId]) (strip-top-level-keys swagger))])) transform-path (fn [[p _ c]] (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] diff --git a/project.clj b/project.clj index 893860b4..50501565 100644 --- a/project.clj +++ b/project.clj @@ -27,24 +27,24 @@ [metosin/reitit-frontend "0.5.18"] [metosin/reitit-sieppari "0.5.18"] [metosin/reitit-pedestal "0.5.18"] - [metosin/ring-swagger-ui "4.3.0"] + [metosin/ring-swagger-ui "4.15.5"] [metosin/spec-tools "0.10.5"] [metosin/schema-tools "0.12.3"] [metosin/muuntaja "0.6.8"] - [metosin/jsonista "0.3.5"] + [metosin/jsonista "0.3.7"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/malli "0.8.2"] + [metosin/malli "0.10.1"] ;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111 - [com.fasterxml.jackson.core/jackson-core "2.13.2"] - [com.fasterxml.jackson.core/jackson-databind "2.13.2.2"] + [com.fasterxml.jackson.core/jackson-core "2.14.1"] + [com.fasterxml.jackson.core/jackson-databind "2.14.1"] [meta-merge "1.0.0"] - [fipp "0.6.25" :exclusions [org.clojure/core.rrb-vector]] + [fipp "0.6.26" :exclusions [org.clojure/core.rrb-vector]] [expound "0.9.0"] [lambdaisland/deep-diff "0.0-47"] [com.bhauman/spell-spec "0.1.2"] - [ring/ring-core "1.9.5"] + [ring/ring-core "1.9.6"] [io.pedestal/pedestal.service "0.5.10"]] @@ -78,7 +78,7 @@ :java-source-paths ["modules/reitit-core/java-src"] - :dependencies [[org.clojure/clojure "1.10.2"] + :dependencies [[org.clojure/clojure "1.11.1"] [org.clojure/clojurescript "1.10.773"] ;; modules dependencies @@ -86,55 +86,55 @@ [metosin/spec-tools "0.10.5"] [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/jsonista "0.3.5"] - [metosin/malli "0.8.9"] + [metosin/jsonista "0.3.7"] + [metosin/malli "0.10.1"] [lambdaisland/deep-diff "0.0-47"] [meta-merge "1.0.0"] [com.bhauman/spell-spec "0.1.2"] [expound "0.9.0"] - [fipp "0.6.25"] + [fipp "0.6.26"] [orchestra "2021.01.01-1"] - [ring "1.9.5"] + [ring "1.9.6"] [ikitommi/immutant-web "3.0.0-alpha1"] [metosin/ring-http-response "0.9.3"] - [metosin/ring-swagger-ui "4.3.0"] + [metosin/ring-swagger-ui "4.15.5"] [criterium "0.4.6"] [org.clojure/test.check "1.1.1"] - [org.clojure/tools.namespace "1.2.0"] + [org.clojure/tools.namespace "1.3.0"] [com.gfredericks/test.chuck "0.2.13"] [io.pedestal/pedestal.service "0.5.10"] - [org.clojure/core.async "1.5.648"] - [manifold "0.2.3"] - [funcool/promesa "6.1.434"] + [org.clojure/core.async "1.6.673"] + [manifold "0.3.0"] + [funcool/promesa "10.0.594"] - [com.clojure-goes-fast/clj-async-profiler "0.5.1"] + [com.clojure-goes-fast/clj-async-profiler "1.0.3"] [ring-cors "0.1.13"] [com.bhauman/rebel-readline "0.1.4"]]} - :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} + :1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]} :perf {:jvm-opts ^:replace ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"] :test-paths ["perf-test/clj"] - :dependencies [[compojure "1.6.2"] - [ring/ring-defaults "0.3.3"] + :dependencies [[compojure "1.7.0"] + [ring/ring-defaults "0.3.4"] [ikitommi/immutant-web "3.0.0-alpha1"] [io.pedestal/pedestal.service "0.5.10"] [io.pedestal/pedestal.jetty "0.5.10"] [calfpath "0.8.1"] - [org.clojure/core.async "1.5.648"] - [manifold "0.2.3"] - [funcool/promesa "6.1.434"] + [org.clojure/core.async "1.6.673"] + [manifold "0.3.0"] + [funcool/promesa "10.0.594"] [metosin/sieppari] [yada "1.2.16"] - [aleph "0.4.6"] - [ring/ring-defaults "0.3.3"] - [ataraxy "0.4.2"] + [aleph "0.6.0"] + [ring/ring-defaults "0.3.4"] + [ataraxy "0.4.3"] [bidi "2.1.6"] [janus "1.3.2"]]} :analyze {:jvm-opts ^:replace ["-server" @@ -142,7 +142,7 @@ "-XX:+PrintCompilation" "-XX:+UnlockDiagnosticVMOptions" "-XX:+PrintInlining"]}} - :aliases {"all" ["with-profile" "dev,default:dev,default,1.9"] + :aliases {"all" ["with-profile" "dev,default:dev,default,1.10"] "perf" ["with-profile" "default,dev,perf"] "test-clj" ["all" "do" ["bat-test"] ["check"]] "test-browser" ["doo" "chrome-headless" "test"] diff --git a/test/clj/cljdoc/reaper.clj b/test/clj/cljdoc/reaper.clj index bce3e68c..cee3dee6 100644 --- a/test/clj/cljdoc/reaper.clj +++ b/test/clj/cljdoc/reaper.clj @@ -30,4 +30,4 @@ (spit "doc/cljdoc.edn" (with-out-str (pprint/pprint data))))) (comment - (reap!)) + (reap!)) diff --git a/test/cljc/reitit/exception_test.cljc b/test/cljc/reitit/exception_test.cljc index bb77421d..77362c44 100644 --- a/test/cljc/reitit/exception_test.cljc +++ b/test/cljc/reitit/exception_test.cljc @@ -1,6 +1,6 @@ (ns reitit.exception-test (:require [clojure.spec.alpha :as s] - [clojure.test :refer [are deftest is testing]] + [clojure.test :refer [are deftest is]] [reitit.core :as r] [reitit.dev.pretty :as pretty] [reitit.exception :as exception] diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 526c140c..0059723b 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -1,5 +1,5 @@ (ns reitit.impl-test - (:require [clojure.test :refer [are deftest is testing]] + (:require [clojure.test :refer [are deftest is]] [reitit.impl :as impl])) (deftest strip-nils-test @@ -50,6 +50,8 @@ {"a" "b"} "a=b" {:a 1} "a=1" {:a nil} "a=" + {:a []} "a=" + {:a '()} "a=" {:a :b :c "d"} "a=b&c=d" {:a "b c"} "a=b+c" {:a ["b" "c"]} "a=b&a=c" diff --git a/test/cljc/reitit/interceptor_test.cljc b/test/cljc/reitit/interceptor_test.cljc index 185db2db..f8bc8b43 100644 --- a/test/cljc/reitit/interceptor_test.cljc +++ b/test/cljc/reitit/interceptor_test.cljc @@ -1,5 +1,5 @@ (ns reitit.interceptor-test - (:require [clojure.test :refer [are deftest is testing]] + (:require [clojure.test :refer [deftest is testing]] [reitit.core :as r] [reitit.interceptor :as interceptor]) #?(:clj diff --git a/test/cljc/reitit/middleware_test.cljc b/test/cljc/reitit/middleware_test.cljc index 0b7bb05e..23426461 100644 --- a/test/cljc/reitit/middleware_test.cljc +++ b/test/cljc/reitit/middleware_test.cljc @@ -1,5 +1,5 @@ (ns reitit.middleware-test - (:require [clojure.test :refer [are deftest is testing]] + (:require [clojure.test :refer [deftest is testing]] [reitit.core :as r] [reitit.middleware :as middleware]) #?(:clj diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index cf4c9092..e8bee456 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -2,7 +2,10 @@ (:require [clojure.test :refer [deftest is testing]] [malli.experimental.lite :as l] #?@(:clj [[muuntaja.middleware] - [jsonista.core :as j]]) + [jsonista.core :as j]]) + [malli.core :as m] + [malli.util :as mu] + [meta-merge.core :refer [meta-merge]] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] @@ -208,6 +211,38 @@ (let [{:keys [status]} (app invalid-request2)] (is (= 500 status)))))))) +(defn- custom-meta-merge-checking-schema + ([] {}) + ([left] left) + ([left right] + (cond + (and (map? left) (map? right)) + (merge-with custom-meta-merge-checking-schema left right) + + (and (m/schema? left) + (m/schema? right)) + (mu/merge left right) + + :else + (meta-merge left right))) + ([left right & more] + (reduce custom-meta-merge-checking-schema left (cons right more)))) + +(defn- custom-meta-merge-checking-parameters + ([] {}) + ([left] left) + ([left right] + (if (and (map? left) (map? right) + (contains? left :parameters) + (contains? right :parameters)) + (-> (merge-with custom-meta-merge-checking-parameters left right) + (assoc :parameters (merge-with mu/merge + (:parameters left) + (:parameters right)))) + (meta-merge left right))) + ([left right & more] + (reduce custom-meta-merge-checking-parameters left (cons right more)))) + (deftest malli-coercion-test (let [create (fn [middleware routes] (ring/ring-handler @@ -524,7 +559,28 @@ (is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}])))) (testing "edn encoding (nada)" - (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))))) + (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))) + + (testing "using custom meta-merge function" + (let [->app (fn [schema-fn meta-merge] + (ring/ring-handler + (ring/router + ["/merging-params/:foo" {:parameters {:path (schema-fn [:map [:foo :string]])}} + ["/:bar" {:parameters {:path (schema-fn [:map [:bar :string]])} + :get {:handler (fn [{{{:keys [foo bar]} :path} :parameters}] + {:status 200 + :body {:total (str "FOO: " foo ", " + "BAR: " bar)}})}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion} + :meta-merge meta-merge}))) + call (fn [schema-fn meta-merge] + ((->app schema-fn meta-merge) {:uri "/merging-params/this/that" + :request-method :get}))] + + (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema))) + (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters))))))) #?(:clj (deftest muuntaja-test diff --git a/test/cljc/reitit/ring_spec_test.cljc b/test/cljc/reitit/ring_spec_test.cljc index 1112342b..5d4e4ee7 100644 --- a/test/cljc/reitit/ring_spec_test.cljc +++ b/test/cljc/reitit/ring_spec_test.cljc @@ -83,7 +83,7 @@ ExceptionInfo #"Invalid route data" (ring/router - ["/api" {:handler identity + ["/api" {:handler identity :middleware '()}] {:validate rrs/validate}))))) diff --git a/test/cljc/reitit/ring_test.cljc b/test/cljc/reitit/ring_test.cljc index d7b9f8f4..7f0a0af3 100644 --- a/test/cljc/reitit/ring_test.cljc +++ b/test/cljc/reitit/ring_test.cljc @@ -1,6 +1,6 @@ (ns reitit.ring-test (:require [clojure.set :as set] - [clojure.test :refer [deftest is testing]] + [clojure.test :refer [are deftest is testing]] [reitit.core :as r] [reitit.middleware :as middleware] [reitit.ring :as ring] @@ -312,7 +312,15 @@ (testing "does not strip slashes" (is (= nil (app {:request-method :get, :uri "/slash-less/"}))) - (is (= nil (app {:request-method :post, :uri "/slash-less/"})))))) + (is (= nil (app {:request-method :post, :uri "/slash-less/"})))) + + (testing "retains query-string in location header" + (are [method uri] + (is (= "/with-slash/?kikka=kukka" + (get-in (app {:request-method method :uri uri :query-string "kikka=kukka"}) + [:headers "Location"]))) + :get "/with-slash" + :post "/with-slash")))) (testing "using :method :strip" (let [app (ring/ring-handler @@ -338,7 +346,17 @@ (testing "strips multiple slashes" (is (= 301 (:status (app {:request-method :get, :uri "/slash-less/////"})))) - (is (= 308 (:status (app {:request-method :post, :uri "/slash-less//"}))))))) + (is (= 308 (:status (app {:request-method :post, :uri "/slash-less//"}))))) + + (testing "retains query-string in location header" + (are [method uri] + (is (= "/slash-less?kikka=kukka" + (get-in (app {:request-method method :uri uri :query-string "kikka=kukka"}) + [:headers "Location"]))) + :get "/slash-less/" + :get "/slash-less//" + :post "/slash-less/" + :post "/slash-less//")))) (testing "without option (equivalent to using :method :both)" (let [app (ring/ring-handler @@ -361,7 +379,19 @@ (testing "strips multiple slashes" (is (= 301 (:status (app {:request-method :get, :uri "/slash-less/////"})))) - (is (= 308 (:status (app {:request-method :post, :uri "/slash-less//"}))))))))) + (is (= 308 (:status (app {:request-method :post, :uri "/slash-less//"}))))) + + (testing "retains query-string in location header" + (are [method uri expected-location] + (is (= expected-location + (get-in (app {:request-method method :uri uri :query-string "kikka=kukka"}) + [:headers "Location"]))) + :get "/with-slash" "/with-slash/?kikka=kukka" + :get "/slash-less/" "/slash-less?kikka=kukka" + :get "/slash-less//" "/slash-less?kikka=kukka" + :post "/with-slash" "/with-slash/?kikka=kukka" + :post "/slash-less/" "/slash-less?kikka=kukka" + :post "/slash-less//" "/slash-less?kikka=kukka")))))) (deftest async-ring-test (let [promise #(let [value (atom ::nil)] @@ -703,6 +733,6 @@ {::trie/trie-compiler compiler})] (dotimes [_ 10] (future - (dotimes [n 100000] - (let [body (:body (app {:request-method :get, :uri (str "/" n)}))] - (is (= body (str n)))))))))))) + (dotimes [n 100000] + (let [body (:body (app {:request-method :get, :uri (str "/" n)}))] + (is (= body (str n)))))))))))) diff --git a/test/cljc/reitit/spec_test.cljc b/test/cljc/reitit/spec_test.cljc index e2717989..740d2df2 100644 --- a/test/cljc/reitit/spec_test.cljc +++ b/test/cljc/reitit/spec_test.cljc @@ -39,7 +39,7 @@ (are [data] (is (thrown-with-msg? ExceptionInfo - #"Call to #'reitit.core/router did not conform to spec" + #"Call to (#')*reitit.core/router did not conform to spec" (r/router data))) @@ -69,7 +69,7 @@ (are [opts] (is (thrown-with-msg? ExceptionInfo - #"Call to #'reitit.core/router did not conform to spec" + #"Call to (#')*reitit.core/router did not conform to spec" (r/router ["/api"] opts))) diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index d1b2c81b..0958b8b1 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -25,11 +25,13 @@ ["/spec" {:coercion spec/coercion} ["/plus/:z" {:patch {:summary "patch" + :operationId "Patch" :handler (constantly {:status 200})} :options {:summary "options" :middleware [{:data {:swagger {:responses {200 {:description "200"}}}}}] :handler (constantly {:status 200})} :get {:summary "plus" + :operationId "GetPlus" :parameters {:query {:x int?, :y int?} :path {:z int?}} :swagger {:responses {400 {:schema {:type "string"} @@ -118,6 +120,7 @@ (app {:request-method :get :uri "/api/schema/plus/3" :query-params {:x "2", :y "1"}}))))) + (testing "swagger-spec" (let [spec (:body (app {:request-method :get :uri "/api/swagger.json"})) @@ -126,6 +129,7 @@ :info {:title "my-api"} :paths {"/api/spec/plus/{z}" {:patch {:parameters [] :summary "patch" + :operationId "Patch" :responses {:default {:description ""}}} :options {:parameters [] :summary "options" @@ -156,7 +160,8 @@ 400 {:schema {:type "string"} :description "kosh"} 500 {:description "fail"}} - :summary "plus"} + :summary "plus" + :operationId "GetPlus"} :post {:parameters [{:in "body", :name "body", :description "", @@ -201,6 +206,7 @@ :responses {200 {:schema {:type "object" :properties {:total {:format "int64" :type "integer"}} + :additionalProperties false :required [:total]} :description ""} 400 {:schema {:type "string"} @@ -224,6 +230,7 @@ :responses {200 {:description "" :schema {:properties {:total {:format "int64" :type "integer"}} + :additionalProperties false :required [:total] :type "object"}} 400 {:schema {:type "string"} diff --git a/test/cljs/reitit/frontend/controllers_test.cljs b/test/cljs/reitit/frontend/controllers_test.cljs index 70fbcd9c..ba7e4410 100644 --- a/test/cljs/reitit/frontend/controllers_test.cljs +++ b/test/cljs/reitit/frontend/controllers_test.cljs @@ -10,11 +10,11 @@ (let [log (atom []) controller-state (atom []) controller-1 {:start (fn [_] (swap! log conj :start-1)) - :stop (fn [_] (swap! log conj :stop-1))} + :stop (fn [_] (swap! log conj :stop-1))} controller-2 {:start (fn [_] (swap! log conj :start-2)) - :stop (fn [_] (swap! log conj :stop-2))} + :stop (fn [_] (swap! log conj :stop-2))} controller-3 {:start (fn [{:keys [foo]}] (swap! log conj [:start-3 foo])) - :stop (fn [{:keys [foo]}] (swap! log conj [:stop-3 foo])) + :stop (fn [{:keys [foo]}] (swap! log conj [:stop-3 foo])) :identity (fn [match] {:foo (-> match :parameters :path :foo)})}] @@ -70,9 +70,9 @@ (let [log (atom []) controller-state (atom []) static {:start (fn [params] (swap! log conj [:start-static])) - :stop (fn [params] (swap! log conj [:stop-static]))} + :stop (fn [params] (swap! log conj [:stop-static]))} controller {:start (fn [params] (swap! log conj [:start params])) - :stop (fn [params] (swap! log conj [:stop params])) + :stop (fn [params] (swap! log conj [:stop params])) :parameters {:path [:foo]}}] (testing "init" diff --git a/test/cljs/reitit/frontend/core_test.cljs b/test/cljs/reitit/frontend/core_test.cljs index e5c1d13a..72bf9428 100644 --- a/test/cljs/reitit/frontend/core_test.cljs +++ b/test/cljs/reitit/frontend/core_test.cljs @@ -56,9 +56,9 @@ (is (= [{:type :warn :message ["missing route" ::asd]}] (:messages - (capture-console - (fn [] - (rf/match-by-name! router ::asd))))))))) + (capture-console + (fn [] + (rf/match-by-name! router ::asd))))))))) (testing "schema coercion" (let [router (r/router ["/" @@ -124,6 +124,6 @@ :required #{:id} :path-params {}}]}] (:messages - (capture-console - (fn [] - (rf/match-by-name! router ::foo {})))))))))) + (capture-console + (fn [] + (rf/match-by-name! router ::foo {})))))))))) diff --git a/test/cljs/reitit/frontend/history_test.cljs b/test/cljs/reitit/frontend/history_test.cljs index cf5341fb..ffab3970 100644 --- a/test/cljs/reitit/frontend/history_test.cljs +++ b/test/cljs/reitit/frontend/history_test.cljs @@ -27,8 +27,8 @@ (is (= "#/bar/5?q=x" (rfh/href history ::bar {:id 5} {:q "x"}))) (let [{:keys [value messages]} (capture-console - (fn [] - (rfh/href history ::asd)))] + (fn [] + (rfh/href history ::asd)))] (is (= nil value)) (is (= [{:type :warn :message ["missing route" ::asd]}] @@ -84,8 +84,8 @@ (is (= "/bar/5?q=x" (rfh/href history ::bar {:id 5} {:q "x"}))) (let [{:keys [value messages]} (capture-console - (fn [] - (rfh/href history ::asd)))] + (fn [] + (rfh/href history ::asd)))] (is (= nil value)) (is (= [{:type :warn :message ["missing route" ::asd]}] @@ -153,7 +153,7 @@ (done))))) {:use-fragment false}) create-link #(doto - (js/document.createElement "A") + (js/document.createElement "A") (.setAttribute "href" (rfh/href history ::foo))) document-link (create-link) shadow-link (create-link)] diff --git a/test/cljs/reitit/frontend/test_utils.cljs b/test/cljs/reitit/frontend/test_utils.cljs index b70c5f37..6bd1163f 100644 --- a/test/cljs/reitit/frontend/test_utils.cljs +++ b/test/cljs/reitit/frontend/test_utils.cljs @@ -10,6 +10,6 @@ (set! js/console.warn (partial log :warn)) (f) (finally - (set! js/console.warn original-console-warn)))] + (set! js/console.warn original-console-warn)))] {:value value :messages @messages}))