diff --git a/router/src/history/location.rs b/router/src/history/location.rs index 77ef91b5e..a9b40ac5f 100644 --- a/router/src/history/location.rs +++ b/router/src/history/location.rs @@ -19,7 +19,7 @@ pub fn create_location(cx: Scope, path: ReadSignal, state: ReadSignal "1".to_string() +/// "id" => "1" /// }; /// assert_eq!(map.get("id"), Some(&"1".to_string())); /// assert_eq!(map.get("missing"), None) @@ -78,7 +78,7 @@ macro_rules! params_map { let start_capacity = common_macros::const_expr_count!($($key);*); #[allow(unused_mut)] let mut map = linear_map::LinearMap::with_capacity(start_capacity); - $( map.insert($key, $val); )* + $( map.insert($key.to_string(), $val.to_string()); )* $crate::ParamsMap(map) }); } diff --git a/router/src/history/url.rs b/router/src/history/url.rs index ce28d7d75..b46f735c9 100644 --- a/router/src/history/url.rs +++ b/router/src/history/url.rs @@ -1,41 +1,20 @@ use crate::ParamsMap; +#[cfg(not(feature = "ssr"))] +use js_sys::{try_iter, Array, JsString}; +#[cfg(not(feature = "ssr"))] +use wasm_bindgen::JsCast; +#[cfg(not(feature = "ssr"))] +use wasm_bindgen::JsValue; #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Url { pub origin: String, pub pathname: String, pub search: String, + pub search_params: ParamsMap, pub hash: String, } -impl Url { - pub fn search_params(&self) -> ParamsMap { - let map = self - .search - .trim_start_matches('?') - .split('&') - .filter_map(|piece| { - let mut parts = piece.split('='); - let (k, v) = (parts.next(), parts.next()); - match k { - Some(k) if !k.is_empty() => { - Some((unescape(k), unescape(v.unwrap_or_default()))) - } - _ => None, - } - }) - .collect::>(); - ParamsMap(map) - } -} - -#[cfg(feature = "ssr")] -pub fn unescape(s: &str) -> String { - urlencoding::decode(s) - .unwrap_or_else(|_| std::borrow::Cow::from(s)) - .replace('+', " ") -} - #[cfg(not(feature = "ssr"))] pub fn unescape(s: &str) -> String { js_sys::decode_uri(s).unwrap().into() @@ -57,12 +36,24 @@ impl TryFrom<&str> for Url { fn try_from(url: &str) -> Result { let fake_host = String::from("http://leptos"); - let url = web_sys::Url::new_with_base(url, &fake_host) - .map_err(|e| e.as_string().unwrap_or_default())?; + let url = web_sys::Url::new_with_base(url, &fake_host).map_js_error()?; Ok(Self { origin: url.origin(), pathname: url.pathname(), search: url.search(), + search_params: ParamsMap( + try_iter(&url.search_params()) + .map_js_error()? + .ok_or("Failed to use URLSearchParams as an iterator".to_string())? + .map(|value| { + let array: Array = value.map_js_error()?.dyn_into().map_js_error()?; + Ok(( + array.get(0).dyn_into::().map_js_error()?.into(), + array.get(1).dyn_into::().map_js_error()?.into(), + )) + }) + .collect::, Self::Error>>()?, + ), hash: url.hash(), }) } @@ -78,7 +69,24 @@ impl TryFrom<&str> for Url { origin: url.origin().unicode_serialization(), pathname: url.path().to_string(), search: url.query().unwrap_or_default().to_string(), + search_params: ParamsMap( + url.query_pairs() + .map(|(key, value)| (key.to_string(), value.to_string())) + .collect::>(), + ), hash: Default::default(), }) } } + +#[cfg(not(feature = "ssr"))] +trait MapJsError { + fn map_js_error(self) -> Result; +} + +#[cfg(not(feature = "ssr"))] +impl MapJsError for Result { + fn map_js_error(self) -> Result { + self.map_err(|e| e.as_string().unwrap_or_default()) + } +} diff --git a/router/tests/matcher.rs b/router/tests/matcher.rs index 6ab551fdb..0afd90879 100644 --- a/router/tests/matcher.rs +++ b/router/tests/matcher.rs @@ -36,7 +36,7 @@ cfg_if! { Some(PathMatch { path: "/foo/abc-123".into(), params: params_map!( - "id".into() => "abc-123".into() + "id" => "abc-123" ) }) ); @@ -72,7 +72,7 @@ cfg_if! { Some(PathMatch { path: "/foo/bar".into(), params: params_map!( - "something".into() => "baz/qux".into() + "something" => "baz/qux" ) }) ); @@ -87,7 +87,7 @@ cfg_if! { Some(PathMatch { path: "/foo/bar".into(), params: params_map!( - "something".into() => "".into() + "something" => "" ) }) ); diff --git a/router/tests/query_params.rs b/router/tests/query_params.rs new file mode 100644 index 000000000..7f7d43028 --- /dev/null +++ b/router/tests/query_params.rs @@ -0,0 +1,52 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ssr")] { + use leptos_router::{Url, params_map}; + + macro_rules! assert_params_map { + ([$($key:expr => $val:expr),*] , $actual:expr) => ( + assert_eq!(params_map!($($key => $val),*), $actual) + ); + } + + #[test] + fn test_param_with_plus_sign() { + let url = Url::try_from("http://leptos.com?data=1%2B2%3D3").unwrap(); + assert_params_map!{ + ["data" => "1+2=3"], + url.search_params + }; + } + + #[test] + fn test_param_with_ampersand() { + let url = Url::try_from("http://leptos.com?data=true+%26+false+%3D+false").unwrap(); + assert_params_map!{ + ["data" => "true & false = false"], + url.search_params + }; + } + + #[test] + fn test_complext_query_string() { + let url = Url::try_from("http://leptos.com?data=Data%3A+%24+%26+%2B%2B+7").unwrap(); + assert_params_map!{ + ["data" => "Data: $ & ++ 7"], + url.search_params + }; + } + + #[test] + fn test_multiple_query_params() { + let url = Url::try_from("http://leptos.com?param1=value1¶m2=value2").unwrap(); + assert_params_map!{ + [ + "param1" => "value1", + "param2" => "value2" + ], + url.search_params + }; + } + } +}