mirror of
https://github.com/metosin/reitit.git
synced 2026-02-20 17:29:08 +00:00
Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c1544c3ce | ||
|
|
0724c0c5a0 | ||
|
|
7051c99e99 | ||
|
|
4c8a69c616 | ||
|
|
e3306e1876 | ||
|
|
e6e1bfd5c4 | ||
|
|
8391fafbe2 | ||
|
|
97bfafa907 | ||
|
|
71a777b4fa | ||
|
|
a6b68cc3d6 | ||
|
|
248200aad3 | ||
|
|
bf18586d75 | ||
|
|
ed6397cd05 |
13 changed files with 155 additions and 33 deletions
|
|
@ -12,6 +12,12 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
|
||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
* **FIX** redirect-trailing-slash-handler won't make external redirects. [#776](https://github.com/metosin/reitit/pull/776)
|
||||
* Allow colons in bracket parameter syntax. [#770](https://github.com/metosin/reitit/pull/770)
|
||||
* Add `url-encode?` option to `match-by-name`. [#778](https://github.com/metosin/reitit/pull/778)
|
||||
|
||||
## 0.10.0 (2026-01-09)
|
||||
|
||||
* Improve & document how response schemas get picked in per-content-type coercion. See [docs](./doc/ring/coercion.md#per-content-type-coercion). [#745](https://github.com/metosin/reitit/issues/745).
|
||||
|
|
|
|||
|
|
@ -75,6 +75,17 @@ Path-parameters are automatically coerced into strings, with the help of (curren
|
|||
; :path-params {:id "1"}}
|
||||
```
|
||||
|
||||
In case you want to do something like generate a template path for documentation, you can disable url-encoding:
|
||||
|
||||
```clj
|
||||
(r/match-by-name router ::user {:id "<id goes here>"} {:url-encode? false})
|
||||
; #reitit.core.Match{:template "/api/user/:id"
|
||||
; :data {:name :user/user}
|
||||
; :path "/api/user/<id goes here>"
|
||||
; :result nil
|
||||
; :path-params {:id "<id goes here>"}}
|
||||
```
|
||||
|
||||
There is also an exception throwing version:
|
||||
|
||||
```clj
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
|
|||
{:status 200
|
||||
:body {:total total}}))})
|
||||
```
|
||||
|
||||
|
||||
### Nested parameter definitions
|
||||
|
||||
Parameters are accumulated recursively along the route tree, just like
|
||||
|
|
@ -94,6 +92,26 @@ handling for merging eg. malli `:map` schemas.
|
|||
; [:task-id :int]]}
|
||||
```
|
||||
|
||||
### Differences in behaviour for different parameters
|
||||
|
||||
All parameter coercions *except* `:body`:
|
||||
|
||||
1. Allow keys outside the schema (by opening up the schema using eg. `malli.util/open-schema`)
|
||||
2. Keywordize the keys (ie. header & query parameter names) of the input before coercing
|
||||
|
||||
In contrast, the `:body` coercion:
|
||||
|
||||
1. Uses the specified schema
|
||||
* depending on the coercion, it can be configured as open or closed, see specific coercion docs for details
|
||||
2. Does not keywordize the keys of the input before coercion
|
||||
* however, coercions like malli might do the keywordization when coercing json bodies, depending on configuration
|
||||
|
||||
This admittedly confusing behaviour is retained currently due to
|
||||
backwards compatibility reasons. It can be configured by passing
|
||||
option `:reitit.coercion/parameter-coercion` to `reitit.ring/router`
|
||||
or `reitit.coercion/compile-request-coercers`. See also:
|
||||
`reitit.coercion/default-parameter-coercion`.
|
||||
|
||||
## Coercion Middleware
|
||||
|
||||
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
[metosin/jsonista "0.3.8"]
|
||||
[ring/ring-jetty-adapter "1.12.1"]
|
||||
[metosin/reitit "0.10.0"]
|
||||
[metosin/ring-swagger-ui "5.9.0"]]
|
||||
[metosin/ring-swagger-ui "5.9.0"]
|
||||
[org.slf4j/slf4j-simple "2.0.9"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||
|
|
|
|||
|
|
@ -156,22 +156,22 @@
|
|||
[:regex [:re "[0-9]+"]]
|
||||
[:enum [:enum 1 3 5 42]]
|
||||
[:multi [:multi {:dispatch :type}
|
||||
[:literal [:map
|
||||
[:type [:= :literal]]
|
||||
["literal" [:map
|
||||
[:type [:= "literal"]]
|
||||
[:value [:or :int :string]]]]
|
||||
[:reference [:map
|
||||
[:type [:= :reference]]
|
||||
["reference" [:map
|
||||
[:type [:= "reference"]]
|
||||
[:description :string]
|
||||
[:ref :uuid]]]]]]
|
||||
:example {:vector-of-tuples [["a" 1] ["b" 2]]
|
||||
:regex "01234"
|
||||
:enum 5
|
||||
:multi {:type :literal
|
||||
:multi {:type "literal"
|
||||
:value "x"}}}}}
|
||||
:responses {200 {:content {:default {:schema [:map]}}}}
|
||||
:responses {200 {:content {:default {:schema [:map-of :keyword :any]}}}}
|
||||
:handler (fn [request]
|
||||
{:status 200
|
||||
:body (:body request)})}}]
|
||||
:body (get-in request [:parameters :request])})}}]
|
||||
|
||||
["/secure"
|
||||
{:tags #{"secure"}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
(options [this])
|
||||
(route-names [this])
|
||||
(match-by-path [this path])
|
||||
(match-by-name [this name] [this name path-params]))
|
||||
(match-by-name [this name] [this name path-params] [this name path-params opts]))
|
||||
|
||||
(defn router? [x]
|
||||
(satisfies? Router x))
|
||||
|
|
@ -122,9 +122,11 @@
|
|||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match nil)))
|
||||
(match-by-name [_ name path-params]
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match (impl/path-params path-params))))))))
|
||||
(match (impl/path-params path-params opts))))))))
|
||||
|
||||
(defn lookup-router
|
||||
"Creates a lookup-router from resolved routes and optional
|
||||
|
|
@ -161,9 +163,11 @@
|
|||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match nil)))
|
||||
(match-by-name [_ name path-params]
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match (impl/path-params path-params))))))))
|
||||
(match (impl/path-params path-params opts))))))))
|
||||
|
||||
(defn trie-router
|
||||
"Creates a special prefix-tree router from resolved routes and optional
|
||||
|
|
@ -208,9 +212,11 @@
|
|||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match nil)))
|
||||
(match-by-name [_ name path-params]
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
(match (impl/path-params path-params))))))))
|
||||
(match (impl/path-params path-params opts))))))))
|
||||
|
||||
(defn single-static-path-router
|
||||
"Creates a fast router of 1 static route(s) and optional
|
||||
|
|
@ -238,8 +244,10 @@
|
|||
(if (#?(:clj .equals :cljs =) p path) match))
|
||||
(match-by-name [_ name]
|
||||
(if (= n name) match))
|
||||
(match-by-name [_ name path-params]
|
||||
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params opts))))))))
|
||||
|
||||
(defn mixed-router
|
||||
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
|
||||
|
|
@ -268,9 +276,11 @@
|
|||
(match-by-name [_ name]
|
||||
(or (match-by-name static-router name)
|
||||
(match-by-name wildcard-router name)))
|
||||
(match-by-name [_ name path-params]
|
||||
(or (match-by-name static-router name path-params)
|
||||
(match-by-name wildcard-router name path-params)))))))
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(or (match-by-name static-router name path-params opts)
|
||||
(match-by-name wildcard-router name path-params opts)))))))
|
||||
|
||||
(defn quarantine-router
|
||||
"Creates two routers: [[mixed-router]] for non-conflicting routes
|
||||
|
|
@ -299,9 +309,11 @@
|
|||
(match-by-name [_ name]
|
||||
(or (match-by-name mixed-router name)
|
||||
(match-by-name linear-router name)))
|
||||
(match-by-name [_ name path-params]
|
||||
(or (match-by-name mixed-router name path-params)
|
||||
(match-by-name linear-router name path-params)))))))
|
||||
(match-by-name [r name path-params]
|
||||
(match-by-name r name path-params nil))
|
||||
(match-by-name [_ name path-params opts]
|
||||
(or (match-by-name mixed-router name path-params opts)
|
||||
(match-by-name linear-router name path-params opts)))))))
|
||||
|
||||
;;
|
||||
;; Creating Routers
|
||||
|
|
|
|||
|
|
@ -297,8 +297,11 @@
|
|||
|
||||
(defn path-params
|
||||
"Convert parameters' values into URL-encoded strings, suitable for URL paths"
|
||||
[params]
|
||||
(maybe-map-values #(url-encode (into-string %)) params))
|
||||
([params] (path-params params nil))
|
||||
([params {:keys [url-encode?] :or {url-encode? true}}]
|
||||
(if url-encode?
|
||||
(maybe-map-values #(url-encode (into-string %)) params)
|
||||
(maybe-map-values #(into-string %) params))))
|
||||
|
||||
(defn- query-parameter [k v]
|
||||
(str (form-encode (into-string k))
|
||||
|
|
|
|||
|
|
@ -71,11 +71,17 @@
|
|||
|
||||
(and bracket? (= \{ c))
|
||||
(let [^long to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))]
|
||||
(if (= \* (get s (inc to)))
|
||||
(cond
|
||||
(= \* (get s (inc to)))
|
||||
(recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to')))
|
||||
|
||||
(= \: (get s (inc to)))
|
||||
(recur (concat ss (-static from to) (-wild (inc to) to')) (long (inc to')) (long (inc to')))
|
||||
|
||||
:else
|
||||
(recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to')))))
|
||||
|
||||
(and colon? (= \: c))
|
||||
(and colon? (= \: c) (not= \{ (get s (dec to))))
|
||||
(let [^long to' (or (str/index-of s "/" to) (count s))]
|
||||
(if (= 1 (- to' to))
|
||||
(recur ss from (inc to))
|
||||
|
|
|
|||
|
|
@ -173,7 +173,8 @@
|
|||
(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" (if query-string (str path "?" query-string) path)}
|
||||
:headers {"Location" (let [path (str/replace-first path #"^/+" "/")] ; Locations starting with // redirect to another hostname. Avoid these due to security implications.
|
||||
(if query-string (str path "?" query-string) path))}
|
||||
:body ""}))
|
||||
(redirect-handler [request]
|
||||
(let [uri (:uri request)]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@
|
|||
:path "/api/ipa/large"
|
||||
:path-params {:size "large"}})
|
||||
(r/match-by-name router ::beer {:size "large"})))
|
||||
(is (= (r/map->Match
|
||||
{:template "/api/ipa/:size"
|
||||
:data {:name ::beer}
|
||||
:path "/api/ipa/:large"
|
||||
:path-params {:size ":large"}})
|
||||
(r/match-by-name router ::beer {:size ":large"} {:url-encode? false})))
|
||||
(is (= (r/map->Match
|
||||
{:template "/api/ipa/:size"
|
||||
:data {:name ::beer}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,33 @@
|
|||
:u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e"
|
||||
:k :kikka
|
||||
:qk ::kikka
|
||||
:nil nil}))))
|
||||
:nil nil})))
|
||||
(is (= {:n "1"
|
||||
:n1 "-1"
|
||||
:n2 "1"
|
||||
:n3 "1"
|
||||
:n4 "1"
|
||||
:n5 "1"
|
||||
:d "2.2"
|
||||
:b "true"
|
||||
:s "kikka"
|
||||
:u "c2541900-17a7-4353-9024-db8ac258ba4e"
|
||||
:k "kikka"
|
||||
:qk "reitit.impl-test/kikka"
|
||||
:nil nil}
|
||||
(impl/path-params {:n 1
|
||||
:n1 -1
|
||||
:n2 (long 1)
|
||||
:n3 (int 1)
|
||||
:n4 (short 1)
|
||||
:n5 (byte 1)
|
||||
:d 2.2
|
||||
:b true
|
||||
:s "kikka"
|
||||
:u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e"
|
||||
:k :kikka
|
||||
:qk ::kikka
|
||||
:nil nil} {:url-encode? false}))))
|
||||
|
||||
(deftest query-params-test
|
||||
(are [x y]
|
||||
|
|
|
|||
|
|
@ -416,7 +416,31 @@
|
|||
: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"))))))
|
||||
:post "/slash-less//" "/slash-less?kikka=kukka"))))
|
||||
|
||||
;; See issue #337
|
||||
(testing "Avoid external redirects"
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router [["*" {:get (constantly nil)}]])
|
||||
(ring/redirect-trailing-slash-handler))
|
||||
resp (fn [uri & [query-string]]
|
||||
(let [r (app {:request-method :get :uri uri :query-string query-string})]
|
||||
{:status (:status r)
|
||||
:Location (get-in r [:headers "Location"])}))]
|
||||
(testing "without query params"
|
||||
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "//malicious.com/foo")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo/")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo//")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "///malicious.com/foo")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo/")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo//"))))
|
||||
(testing "with query params"
|
||||
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "//malicious.com/foo" "bar=quux")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo/" "bar=quux")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo//" "bar=quux")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "///malicious.com/foo" "bar=quux")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo/" "bar=quux")))
|
||||
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo//" "bar=quux"))))))))
|
||||
|
||||
(deftest async-ring-test
|
||||
(let [promise #(let [value (atom ::nil)]
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@
|
|||
|
||||
"/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"]
|
||||
"/olipa/{:kerran}/avaruus", ["/olipa/{:kerran}/avaruus"]
|
||||
"/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"]
|
||||
"/olipa/{:a.b/c}/avaruus", ["/olipa/{:a.b/c}/avaruus"]
|
||||
"/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
|
||||
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "avaruus}"))]
|
||||
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "valtavan.suuri/avaruus}"))])))
|
||||
|
|
@ -53,7 +55,9 @@
|
|||
|
||||
"/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"]
|
||||
"/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{:kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
|
||||
"/olipa/{:a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
|
||||
"/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"]
|
||||
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
|
||||
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)])))
|
||||
|
|
@ -65,7 +69,9 @@
|
|||
|
||||
"/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{:kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
|
||||
"/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
|
||||
"/olipa/{:a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
|
||||
"/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
|
||||
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
|
||||
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)])))
|
||||
|
|
@ -77,7 +83,9 @@
|
|||
|
||||
"/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"]
|
||||
"/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"]
|
||||
"/olipa/{:kerran}/avaruus", ["/olipa/{:kerran}/avaruus"]
|
||||
"/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"]
|
||||
"/olipa/{:a.b/c}/avaruus", ["/olipa/{:a.b/c}/avaruus"]
|
||||
"/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"]
|
||||
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/{*avaruus}"]
|
||||
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{*valtavan.suuri/avaruus}"]))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue