mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Move query string coercion to coercion ns from core
This commit is contained in:
parent
ce6d9e26cd
commit
7ae118fbb5
5 changed files with 80 additions and 59 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
(ns reitit.coercion
|
(ns reitit.coercion
|
||||||
(:require [#?(:clj reitit.walk :cljs clojure.walk) :as walk]
|
(:require [#?(:clj reitit.walk :cljs clojure.walk) :as walk]
|
||||||
|
[reitit.core :as r]
|
||||||
[reitit.impl :as impl])
|
[reitit.impl :as impl])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import (java.io Writer))))
|
(:import (java.io Writer))))
|
||||||
|
|
@ -220,3 +221,33 @@
|
||||||
[match]
|
[match]
|
||||||
(if-let [coercers (-> match :result :coerce)]
|
(if-let [coercers (-> match :result :coerce)]
|
||||||
(coerce-request coercers match)))
|
(coerce-request coercers match)))
|
||||||
|
|
||||||
|
(defn coerce-query-params
|
||||||
|
"Uses an input schema and coercion implementation from the given match to
|
||||||
|
encode query-parameters map.
|
||||||
|
|
||||||
|
If no match, no input schema or coercion implementation, just returns the
|
||||||
|
original parameters map."
|
||||||
|
[match query-params]
|
||||||
|
(when query-params
|
||||||
|
(let [coercion (-> match :data :coercion)
|
||||||
|
schema (when coercion
|
||||||
|
(-compile-model coercion (-> match :data :parameters :query) nil))
|
||||||
|
coercer (when (and schema coercion)
|
||||||
|
(-query-string-coercer coercion schema))]
|
||||||
|
(if coercer
|
||||||
|
(let [result (coercer query-params :default)]
|
||||||
|
(if (error? result)
|
||||||
|
(throw (ex-info (str "Query parameters coercion failed")
|
||||||
|
result))
|
||||||
|
result))
|
||||||
|
query-params))))
|
||||||
|
|
||||||
|
(defn match->path
|
||||||
|
"Create routing path from given match and optional query-parameters map.
|
||||||
|
|
||||||
|
Query-parameters are encoded using the input schema and coercion implementation."
|
||||||
|
([match]
|
||||||
|
(r/match->path match))
|
||||||
|
([match query-params]
|
||||||
|
(r/match->path match (coerce-query-params match query-params))))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
(ns reitit.core
|
(ns reitit.core
|
||||||
(:require [reitit.exception :as exception]
|
(:require [reitit.exception :as exception]
|
||||||
[reitit.impl :as impl]
|
[reitit.impl :as impl]
|
||||||
[reitit.trie :as trie]
|
[reitit.trie :as trie]))
|
||||||
;; FIXME: Should avoid coercion require here?
|
|
||||||
[reitit.coercion :as coercion]))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Expand
|
;; Expand
|
||||||
|
|
@ -70,25 +68,12 @@
|
||||||
(:template match) (:required match) path-params)))))
|
(:template match) (:required match) path-params)))))
|
||||||
|
|
||||||
(defn match->path
|
(defn match->path
|
||||||
|
"Create routing path from given match and optional query-parameters map."
|
||||||
([match]
|
([match]
|
||||||
(match->path match nil))
|
(match->path match nil))
|
||||||
([match query-params]
|
([match query-params]
|
||||||
(some-> match :path (cond-> (seq query-params)
|
(some-> match :path (cond-> (seq query-params)
|
||||||
;; TODO: Should the coercion be applied elsewhere (FE ns?) so the core ns doesn't depend
|
(str "?" (impl/query-string query-params))))))
|
||||||
;; on the coercion?
|
|
||||||
;; NOTE: Re-creates coercer on every call, could this be pre-compiled somewhere
|
|
||||||
;; or memoized? Does it matter much?
|
|
||||||
;; TODO: query-coercer could be compiled in reitit.frontend/router, same as request coercers.
|
|
||||||
(str "?" (let [coercion (-> match :data :coercion)
|
|
||||||
schema (when coercion
|
|
||||||
(coercion/-compile-model coercion (-> match :data :parameters :query) nil))
|
|
||||||
coercer (when (and schema coercion)
|
|
||||||
(coercion/-query-string-coercer coercion schema))
|
|
||||||
query-params (if coercer
|
|
||||||
(coercer query-params :default)
|
|
||||||
query-params)]
|
|
||||||
;; Default encoding for values will handle values that aren't encoded using coercer
|
|
||||||
(impl/query-string query-params)))))))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Different routers
|
;; Different routers
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,16 @@
|
||||||
(defn
|
(defn
|
||||||
^{:see-also ["reitit.core/match->path"]}
|
^{:see-also ["reitit.core/match->path"]}
|
||||||
match->path
|
match->path
|
||||||
"Create routing path from given match and optional query-string map and
|
"Create routing path from given match and optional query-parameters map and
|
||||||
optional fragment string."
|
optional fragment string.
|
||||||
|
|
||||||
|
Query-parameters are encoded using the input schema and coercion implementation."
|
||||||
([match]
|
([match]
|
||||||
(match->path match nil nil))
|
(match->path match nil nil))
|
||||||
([match query-params]
|
([match query-params]
|
||||||
(match->path match query-params nil))
|
(match->path match query-params nil))
|
||||||
([match query-params fragment]
|
([match query-params fragment]
|
||||||
(when-let [path (r/match->path match query-params)]
|
(when-let [path (coercion/match->path match query-params)]
|
||||||
(cond-> path
|
(cond-> path
|
||||||
(and fragment (seq fragment)) (str "#" (impl/form-encode fragment))))))
|
(and fragment (seq fragment)) (str "#" (impl/form-encode fragment))))))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,13 +160,13 @@
|
||||||
(is (= "/olipa/kerran?x=a&x=b"
|
(is (= "/olipa/kerran?x=a&x=b"
|
||||||
(-> router
|
(-> router
|
||||||
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
||||||
(r/match->path {:x [:a :b]}))))
|
(coercion/match->path {:x [:a :b]}))))
|
||||||
|
|
||||||
(is (= "/olipa/kerran?x=a&x=b&extra=extra-param"
|
(is (= "/olipa/kerran?x=a&x=b&extra=extra-param"
|
||||||
(-> router
|
(-> router
|
||||||
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
||||||
(r/match->path {:x [:a :b]
|
(coercion/match->path {:x [:a :b]
|
||||||
:extra "extra-param"}))))))
|
:extra "extra-param"}))))))
|
||||||
|
|
||||||
(testing "custom encode/string for a collection"
|
(testing "custom encode/string for a collection"
|
||||||
(let [router (r/router ["/:a/:b"
|
(let [router (r/router ["/:a/:b"
|
||||||
|
|
@ -182,18 +182,20 @@
|
||||||
(mapv keyword (str/split s #","))
|
(mapv keyword (str/split s #","))
|
||||||
s))}
|
s))}
|
||||||
:keyword]]]}}]
|
:keyword]]]}}]
|
||||||
{:compile coercion/compile-request-coercers})]
|
{:compile coercion/compile-request-coercers})
|
||||||
;; NOTE: "," is urlencoded by the impl/query-string step, is that ok?
|
match (r/match-by-name! router ::route {:a "olipa", :b "kerran"})]
|
||||||
|
(is (= {:x "a,b"}
|
||||||
|
(coercion/coerce-query-params match {:x [:a :b]})))
|
||||||
|
|
||||||
|
;; NOTE: "," is urlencoded by the impl/query-string step
|
||||||
(is (= "/olipa/kerran?x=a%2Cb"
|
(is (= "/olipa/kerran?x=a%2Cb"
|
||||||
(-> router
|
(coercion/match->path match {:x [:a :b]})))
|
||||||
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
|
||||||
(r/match->path {:x [:a :b]}))))
|
|
||||||
|
|
||||||
(testing "extra query-string parameters aren't removed by coercion"
|
(testing "extra query-string parameters aren't removed by coercion"
|
||||||
(is (= "/olipa/kerran?x=a%2Cb&extra=extra-param"
|
(is (= "/olipa/kerran?x=a%2Cb&extra=extra-param"
|
||||||
(-> router
|
(-> router
|
||||||
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
||||||
(r/match->path {:x [:a :b]
|
(coercion/match->path {:x [:a :b]
|
||||||
:extra "extra-param"})))))
|
:extra "extra-param"})))))
|
||||||
|
|
||||||
(is (= {:query {:x [:a :b]}}
|
(is (= {:query {:x [:a :b]}}
|
||||||
|
|
@ -220,7 +222,7 @@
|
||||||
(is (= "/olipa/kerran?x=__a&x=__b"
|
(is (= "/olipa/kerran?x=__a&x=__b"
|
||||||
(-> router
|
(-> router
|
||||||
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
|
||||||
(r/match->path {:x [:a :b]}))))
|
(coercion/match->path {:x [:a :b]}))))
|
||||||
|
|
||||||
(is (= {:query {:x [:a :b]}}
|
(is (= {:query {:x [:a :b]}}
|
||||||
(-> (r/match-by-path router "/olipa/kerran")
|
(-> (r/match-by-path router "/olipa/kerran")
|
||||||
|
|
|
||||||
|
|
@ -40,74 +40,75 @@
|
||||||
(fn on-navigate [match history]
|
(fn on-navigate [match history]
|
||||||
(let [url (rfh/-get-path history)]
|
(let [url (rfh/-get-path history)]
|
||||||
(case (swap! n inc)
|
(case (swap! n inc)
|
||||||
1 (do (is (some? (:popstate-listener history)))
|
1 (rfh/push-state history ::frontpage)
|
||||||
|
2 (do (is (some? (:popstate-listener history)))
|
||||||
(is (= "/" url)
|
(is (= "/" url)
|
||||||
"start at root")
|
"start at root")
|
||||||
(rfe/push-state ::foo nil {:a 1} "foo bar"))
|
(rfe/push-state ::foo nil {:a 1} "foo bar"))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
;; 1. /foo?a=1#foo+bar
|
;; 1. /foo?a=1#foo+bar
|
||||||
2 (do (is (= "/foo?a=1#foo+bar" url)
|
3 (do (is (= "/foo?a=1#foo+bar" url)
|
||||||
"push-state")
|
"push-state")
|
||||||
(.back js/window.history))
|
(.back js/window.history))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
3 (do (is (= "/" url)
|
4 (do (is (= "/" url)
|
||||||
"go back")
|
"go back")
|
||||||
(rfe/navigate ::bar {:path-params {:id 1}
|
(rfe/navigate ::bar {:path-params {:id 1}
|
||||||
:query-params {:q "x"}}))
|
:query-params {:q "x"}}))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
;; 1. /bar/1
|
;; 1. /bar/1
|
||||||
4 (do (is (= "/bar/1?q=__x" url)
|
5 (do (is (= "/bar/1?q=__x" url)
|
||||||
"push-state 2")
|
"push-state 2")
|
||||||
(rfe/replace-state ::bar {:id 2}))
|
(rfe/replace-state ::bar {:id 2}))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
;; 1. /bar/2
|
;; 1. /bar/2
|
||||||
5 (do (is (= "/bar/2" url)
|
6 (do (is (= "/bar/2" url)
|
||||||
"replace-state")
|
"replace-state")
|
||||||
(rfe/set-query {:a 1}))
|
(rfe/set-query {:a 1}))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
;; 1. /bar/2
|
;; 1. /bar/2
|
||||||
;; 2. /bar/2?a=1
|
;; 2. /bar/2?a=1
|
||||||
6 (do (is (= "/bar/2?a=1" url)
|
7 (do (is (= "/bar/2?a=1" url)
|
||||||
"update-query with map")
|
"update-query with map")
|
||||||
(rfe/set-query #(assoc % :q "x") {:replace true}))
|
(rfe/set-query #(assoc % :q "x") {:replace true}))
|
||||||
;; 0. /
|
;; 0. /
|
||||||
;; 1. /bar/2
|
;; 1. /bar/2
|
||||||
;; 2. /bar/2?a=1&b=foo
|
;; 2. /bar/2?a=1&b=foo
|
||||||
7 (do (is (= "/bar/2?a=1&q=__x" url)
|
8 (do (is (= "/bar/2?a=1&q=__x" url)
|
||||||
"update-query with fn")
|
"update-query with fn")
|
||||||
(.go js/window.history -2))
|
(.go js/window.history -2))
|
||||||
|
|
||||||
;; Go to non-matching path and check set-query works
|
;; Go to non-matching path and check set-query works
|
||||||
;; (without coercion) without a match
|
;; (without coercion) without a match
|
||||||
8 (do (is (= "/" url) "go back two events")
|
9 (do (is (= "/" url) "go back two events")
|
||||||
(.pushState js/window.history nil "" "#/non-matching-path"))
|
(.pushState js/window.history nil "" "#/non-matching-path"))
|
||||||
|
|
||||||
9 (do (is (= "/non-matching-path" url))
|
10 (do (is (= "/non-matching-path" url))
|
||||||
(rfe/set-query #(assoc % :q "x")))
|
(rfe/set-query #(assoc % :q "x")))
|
||||||
|
|
||||||
10 (do (is (= "/non-matching-path?q=x" url))
|
11 (do (is (= "/non-matching-path?q=x" url))
|
||||||
(.go js/window.history -2))
|
(.go js/window.history -2))
|
||||||
|
|
||||||
;; 0. /
|
;; 0. /
|
||||||
11 (do (is (= "/" url)
|
12 (do (is (= "/" url)
|
||||||
"go back two events")
|
"go back two events")
|
||||||
|
|
||||||
;; Reset to ensure old event listeners aren't called
|
;; Reset to ensure old event listeners aren't called
|
||||||
(rfe/start! router
|
(rfe/start! router
|
||||||
(fn on-navigate [match history]
|
(fn on-navigate [match history]
|
||||||
(let [url (rfh/-get-path history)]
|
(let [url (rfh/-get-path history)]
|
||||||
(case (swap! n inc)
|
(case (swap! n inc)
|
||||||
12 (do (is (= "/" url)
|
13 (do (is (= "/" url)
|
||||||
"start at root")
|
"start at root")
|
||||||
(rfe/push-state ::foo))
|
(rfe/push-state ::foo))
|
||||||
13 (do (is (= "/foo" url)
|
14 (do (is (= "/foo" url)
|
||||||
"push-state")
|
"push-state")
|
||||||
(rfh/stop! @rfe/history)
|
(rfh/stop! @rfe/history)
|
||||||
(done))
|
(done))
|
||||||
(do
|
(do
|
||||||
(is false (str "extra event 2" {:n @n, :url url}))
|
(is false (str "extra event 2" {:n @n, :url url}))
|
||||||
(done)))))
|
(done)))))
|
||||||
{:use-fragment true}))
|
{:use-fragment true}))
|
||||||
(do
|
(do
|
||||||
(is false (str "extra event 1" {:n @n, :url url}))
|
(is false (str "extra event 1" {:n @n, :url url}))
|
||||||
(done)))))
|
(done)))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue