diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 4436214c..75312c86 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -39,7 +39,8 @@ :body (->ParameterCoercion :body-params :body false false) :form (->ParameterCoercion :form-params :string true true) :header (->ParameterCoercion :headers :string true true) - :path (->ParameterCoercion :path-params :string true true)}) + :path (->ParameterCoercion :path-params :string true true) + :fragment (->ParameterCoercion :fragment-params :string true true)}) (defn ^:no-doc request-coercion-failed! [result coercion value in request serialize-failed-result] (throw diff --git a/modules/reitit-frontend/src/reitit/frontend.cljs b/modules/reitit-frontend/src/reitit/frontend.cljs index d1e9c50a..922b5a8b 100644 --- a/modules/reitit-frontend/src/reitit/frontend.cljs +++ b/modules/reitit-frontend/src/reitit/frontend.cljs @@ -1,5 +1,6 @@ (ns reitit.frontend (:require [clojure.set :as set] + [clojure.string :as str] [reitit.coercion :as coercion] [reitit.core :as r]) (:import goog.Uri @@ -20,6 +21,19 @@ (map (juxt keyword #(query-param q %))) (into {})))) +(defn fragment-params + "Given goog.Uri, read fragment parameters into Clojure map." + [^Uri uri] + (let [fp (.getFragment uri)] + (if-not (seq fp) + {} + (into {} + (comp + (map #(str/split % #"=")) + (map (fn [[k v]] + [(keyword k) v]))) + (str/split fp #"&"))))) + (defn match-by-path "Given routing tree and current path, return match with possibly coerced parameters. Return nil if no match found. @@ -37,12 +51,14 @@ coercion/coerce!)] (if-let [match (r/match-by-path router (.getPath uri))] (let [q (query-params uri) - match (assoc match :query-params q) + fp (fragment-params uri) + match (assoc match :query-params q :fragment-params fp) ;; Return uncoerced values if coercion is not enabled - so ;; that tha parameters are always accessible from same property. parameters (or (coerce! match) {:path (:path-params match) - :query q})] + :query q + :fragment fp})] (assoc match :parameters parameters)))))) (defn match-by-name diff --git a/test/cljs/reitit/frontend/core_test.cljs b/test/cljs/reitit/frontend/core_test.cljs index 72bf9428..42a45bde 100644 --- a/test/cljs/reitit/frontend/core_test.cljs +++ b/test/cljs/reitit/frontend/core_test.cljs @@ -21,9 +21,11 @@ :data {:name ::frontpage} :path-params {} :query-params {} + :fragment-params {} :path "/" :parameters {:query {} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/"))) (is (= "/" @@ -34,9 +36,11 @@ :data {:name ::foo} :path-params {} :query-params {} + :fragment-params {} :path "/foo" :parameters {:query {} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/foo"))) (is (= (r/map->Match @@ -44,9 +48,11 @@ :data {:name ::foo} :path-params {} :query-params {:mode ["foo", "bar"]} + :fragment-params {} :path "/foo" :parameters {:query {:mode ["foo", "bar"]} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/foo?mode=foo&mode=bar"))) (is (= "/foo" @@ -64,7 +70,12 @@ (let [router (r/router ["/" [":id" {:name ::foo :parameters {:path {:id s/Int} - :query {(s/optional-key :mode) s/Keyword}}}]] + :query {(s/optional-key :mode) s/Keyword} + :fragment {(s/optional-key :access_token) s/Str + (s/optional-key :refresh_token) s/Str + (s/optional-key :expires_in) s/Int + (s/optional-key :provider_token) s/Str + (s/optional-key :token_type) s/Str}}}]] {:compile rc/compile-request-coercers :data {:coercion rsc/coercion}})] @@ -72,9 +83,11 @@ {:template "/:id" :path-params {:id "5"} :query-params {} + :fragment-params {} :path "/5" :parameters {:query {} - :path {:id 5}}}) + :path {:id 5} + :fragment {}}}) (m (rf/match-by-path router "/5")))) (is (= "/5" @@ -98,23 +111,35 @@ {:template "/:id" :path-params {:id "5"} :query-params {:mode "foo"} + :fragment-params {} :path "/5" :parameters {:path {:id 5} - :query {:mode :foo}}}) + :query {:mode :foo} + :fragment {}}}) (m (rf/match-by-path router "/5?mode=foo")))) (is (= "/5?mode=foo" (r/match->path (rf/match-by-name router ::foo {:id 5}) {:mode :foo})))) - (testing "fragment is ignored" + (testing "fragment is read" (is (= (r/map->Match {:template "/:id" :path-params {:id "5"} :query-params {:mode "foo"} + :fragment-params {:access_token "foo" + :refresh_token "bar" + :provider_token "baz" + :token_type "bearer" + :expires_in "3600"} :path "/5" :parameters {:path {:id 5} - :query {:mode :foo}}}) - (m (rf/match-by-path router "/5?mode=foo#fragment"))))) + :query {:mode :foo} + :fragment {:access_token "foo" + :refresh_token "bar" + :provider_token "baz" + :token_type "bearer" + :expires_in 3600}}}) + (m (rf/match-by-path router "/5?mode=foo#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600"))))) (testing "console warning about missing params" (is (= [{:type :warn