Ensure extra query-string params aren't removed by coercion

This commit is contained in:
Juho Teperi 2025-01-22 11:02:43 +02:00
parent 5f10465533
commit 21e5840f13
4 changed files with 70 additions and 19 deletions

View file

@ -178,6 +178,12 @@
(-response-coercer [_ schema]
(-coercer schema :response transformers :encode opts))
(-query-string-coercer [_ schema]
(-coercer schema :string transformers :encode opts))))))
;; TODO: Create encoding function that only does encode, no decoding and validation?
(-coercer (mu/open-schema schema)
:string
;; Tune transformer to not strip extra keys
{:string {:default (-transformer string-transformer-provider (assoc opts :strip-extra-keys false))}}
:encode
opts))))))
(def coercion (create default-options))

View file

@ -160,7 +160,13 @@
(is (= "/olipa/kerran?x=a&x=b"
(-> router
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
(r/match->path {:x [:a :b]}))))))
(r/match->path {:x [:a :b]}))))
(is (= "/olipa/kerran?x=a&x=b&extra=extra-param"
(-> router
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
(r/match->path {:x [:a :b]
:extra "extra-param"}))))))
(testing "custom encode/string for a collection"
(let [router (r/router ["/:a/:b"
@ -183,6 +189,13 @@
(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"
(is (= "/olipa/kerran?x=a%2Cb&extra=extra-param"
(-> router
(r/match-by-name! ::route {:a "olipa", :b "kerran"})
(r/match->path {:x [:a :b]
:extra "extra-param"})))))
(is (= {:query {:x [:a :b]}}
(-> (r/match-by-path router "/olipa/kerran")
(assoc :query-params {:x "a,b"})

View file

@ -1,16 +1,30 @@
(ns reitit.frontend.easy-test
(:require [clojure.test :refer [deftest testing is are async]]
(:require [clojure.string :as str]
[clojure.test :refer [are async deftest is testing]]
[goog.events :as gevents]
[reitit.coercion.malli :as rcm]
[reitit.core :as r]
[reitit.frontend.easy :as rfe]
[reitit.frontend.history :as rfh]
[goog.events :as gevents]))
[reitit.frontend.history :as rfh]))
(def browser (exists? js/window))
(def router (r/router ["/"
["" ::frontpage]
["foo" ::foo]
["bar/:id" ::bar]]))
["bar/:id"
{:name ::bar
:coercion rcm/coercion
:parameters {:query [:map
[:q {:optional true}
[:keyword
{:decode/string (fn [s]
(if (string? s)
(keyword (if (str/starts-with? s "__")
(subs s 2)
s))
s))
:encode/string (fn [k] (str "__" (name k)))}]]]}}]]))
;; TODO: Only tests fragment history, also test HTML5?
@ -38,10 +52,11 @@
;; 0. /
3 (do (is (= "/" url)
"go back")
(rfe/navigate ::bar {:path-params {:id 1}}))
(rfe/navigate ::bar {:path-params {:id 1}
:query-params {:q "x"}}))
;; 0. /
;; 1. /bar/1
4 (do (is (= "/bar/1" url)
4 (do (is (= "/bar/1?q=__x" url)
"push-state 2")
(rfe/replace-state ::bar {:id 2}))
;; 0. /

View file

@ -3,14 +3,28 @@
[reitit.core :as r]
[reitit.frontend.history :as rfh]
[reitit.frontend.test-utils :refer [capture-console]]
[goog.events :as gevents]))
[goog.events :as gevents]
[reitit.coercion.malli :as rcm]
[clojure.string :as str]))
(def browser (exists? js/window))
(def router (r/router ["/"
["" ::frontpage]
["foo" ::foo]
["bar/:id" ::bar]]))
["bar/:id"
{:name ::bar
:coercion rcm/coercion
:parameters {:query [:map
[:q {:optional true}
[:keyword
{:decode/string (fn [s]
(if (string? s)
(keyword (if (str/starts-with? s "__")
(subs s 2)
s))
s))
:encode/string (fn [k] (str "__" (name k)))}]]]}}]]))
(deftest fragment-history-test
(when browser
@ -24,9 +38,12 @@
(rfh/href history ::foo)))
(is (= "#/bar/5"
(rfh/href history ::bar {:id 5})))
(is (= "#/bar/5?q=x"
(testing "query string coercion doesn't strip extra keys"
(is (= "#/bar/5?extra=a"
(rfh/href history ::bar {:id 5} {:extra "a"}))))
(is (= "#/bar/5?q=__x"
(rfh/href history ::bar {:id 5} {:q "x"})))
(is (= "#/bar/5?q=x#foo"
(is (= "#/bar/5?q=__x#foo"
(rfh/href history ::bar {:id 5} {:q "x"} "foo")))
(let [{:keys [value messages]} (capture-console
(fn []
@ -58,11 +75,11 @@
(.back js/window.history))
4 (do (is (= "/" url)
"go back")
(rfh/push-state history ::bar {:id 1}))
5 (do (is (= "/bar/1" url)
(rfh/push-state history ::bar {:id 1} {:extra "a"}))
5 (do (is (= "/bar/1?extra=a" url)
"push-state 2")
(rfh/replace-state history ::bar {:id 2}))
6 (do (is (= "/bar/2" url)
(rfh/replace-state history ::bar {:id 2} {:q "x"}))
6 (do (is (= "/bar/2?q=__x" url)
"replace-state")
(.back js/window.history))
7 (do (is (= "/" url)
@ -84,7 +101,7 @@
(rfh/href history ::foo)))
(is (= "/bar/5"
(rfh/href history ::bar {:id 5})))
(is (= "/bar/5?q=x"
(is (= "/bar/5?q=__x"
(rfh/href history ::bar {:id 5} {:q "x"})))
(let [{:keys [value messages]} (capture-console
(fn []
@ -119,8 +136,8 @@
(rfh/push-state history ::bar {:id 1}))
5 (do (is (= "/bar/1" url)
"push-state 2")
(rfh/replace-state history ::bar {:id 2}))
6 (do (is (= "/bar/2" url)
(rfh/replace-state history ::bar {:id 2} {:q "x"}))
6 (do (is (= "/bar/2?q=__x" url)
"replace-state")
(.back js/window.history))
7 (do (is (= "/" url)