Skip to content

Commit

Permalink
Fix Routable::parent with hash segments and query params
Browse files Browse the repository at this point in the history
  • Loading branch information
ealmloff committed Jan 2, 2025
1 parent 04282ef commit 3798f96
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 20 deletions.
51 changes: 31 additions & 20 deletions packages/router/src/routable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,17 +625,31 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// ```
fn is_child_of(&self, other: &Self) -> bool {
let self_str = self.to_string();
let self_str = self_str.trim_matches('/');
let (self_str, _) = self_str.split_once('#').unwrap_or((&self_str, ""));
let (self_str, _) = self_str.split_once('?').unwrap_or((&self_str, ""));
let self_str = self_str.trim_end_matches('/');
let other_str = other.to_string();
let other_str = other_str.trim_matches('/');
if other_str.is_empty() {
return true;
}
let self_segments = self_str.split('/');
let other_segments = other_str.split('/');
for (self_seg, other_seg) in self_segments.zip(other_segments) {
if self_seg != other_seg {
return false;
let (other_str, _) = other_str.split_once('#').unwrap_or((&other_str, ""));
let (other_str, _) = other_str.split_once('?').unwrap_or((&other_str, ""));
let other_str = other_str.trim_end_matches('/');

let mut self_segments = self_str.split('/');
let mut other_segments = other_str.split('/');
loop {
match dbg!((self_segments.next(), other_segments.next())) {
// If the two routes are the same length, or this route has less segments, then this segment
// cannot be the child of the other segment
(None, Some(_)) | (None, None) => {
return false;
}
// If two segments are not the same, then this segment cannot be the child of the other segment
(Some(self_seg), Some(other_seg)) => {
if self_seg != other_seg {
return false;
}
}
// If the other route has less segments, then this route is the child of the other route
(Some(_), None) => break,
}
}
true
Expand Down Expand Up @@ -667,17 +681,14 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// ```
fn parent(&self) -> Option<Self> {
let as_str = self.to_string();
let as_str = as_str.trim_matches('/');
let segments = as_str.split('/');
let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
let (route, _) = route_and_query
.split_once('?')
.unwrap_or((route_and_query, ""));
let route = route.trim_end_matches('/');
let segments = route.split_inclusive('/');
let segment_count = segments.clone().count();
let new_route = segments
.take(segment_count - 1)
.fold(String::new(), |mut acc, segment| {
acc.push('/');
acc.push_str(segment);
acc
});

let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
Self::from_str(&new_route).ok()
}

Expand Down
184 changes: 184 additions & 0 deletions packages/router/tests/parent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#![allow(unused)]

use std::rc::Rc;

use dioxus::prelude::*;

#[derive(Routable, Clone, PartialEq, Debug)]
#[rustfmt::skip]
enum Route {
#[route("/")]
RootIndex {},
#[nest("/fixed")]
#[layout(Fixed)]
#[route("/")]
FixedIndex {},
#[route("/fixed")]
FixedFixed {},
#[end_layout]
#[end_nest]
#[nest("/:id")]
#[layout(Parameter)]
#[route("/")]
ParameterIndex { id: u8 },
#[route("/fixed")]
ParameterFixed { id: u8 },
#[end_layout]
#[end_nest]
#[nest("/hash")]
#[route("/")]
HashIndex {},
#[nest("/:id")]
#[route("/?:query")]
HashId { id: u8, query: String },
#[layout(Parameter)]
#[route("/path/?:query#:hash")]
HashQuery { id: u8, query: String, hash: String },
}

#[test]
fn get_parent() {
assert_eq!(Route::RootIndex {}.parent(), None);
assert_eq!(Route::FixedIndex {}.parent(), Some(Route::RootIndex {}));
assert_eq!(Route::FixedFixed {}.parent(), Some(Route::FixedIndex {}));
assert_eq!(
Route::ParameterIndex { id: 0 }.parent(),
Some(Route::RootIndex {})
);
assert_eq!(
Route::ParameterFixed { id: 0 }.parent(),
Some(Route::ParameterIndex { id: 0 })
);
assert_eq!(
Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.parent(),
Some(Route::HashId {
id: 0,
query: "".into()
})
);
assert_eq!(
Route::HashId {
id: 0,
query: "query".into()
}
.parent(),
Some(Route::HashIndex {})
);
assert_eq!(Route::HashIndex {}.parent(), Some(Route::RootIndex {}));
}

#[test]
fn is_child() {
assert!(!Route::RootIndex {}.is_child_of(&Route::RootIndex {}));
assert!(Route::FixedIndex {}.is_child_of(&Route::RootIndex {}));
assert!(!Route::FixedIndex {}.is_child_of(&Route::FixedIndex {}));
assert!(Route::FixedFixed {}.is_child_of(&Route::FixedIndex {}));
assert!(!Route::FixedFixed {}.is_child_of(&Route::FixedFixed {}));
assert!(Route::ParameterIndex { id: 0 }.is_child_of(&Route::RootIndex {}));
assert!(!Route::ParameterIndex { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));
assert!(Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));
assert!(!Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterFixed { id: 0 }));
assert!(Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.is_child_of(&Route::HashId {
id: 0,
query: "query".into()
}));
assert!(!Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.is_child_of(&Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}));
assert!(Route::HashId {
id: 0,
query: "query".into()
}
.is_child_of(&Route::HashIndex {}));
assert!(!Route::HashId {
id: 0,
query: "query".into()
}
.is_child_of(&Route::HashId {
id: 0,
query: "query".into()
}));
assert!(Route::HashIndex {}.is_child_of(&Route::RootIndex {}));
assert!(!Route::HashIndex {}.is_child_of(&Route::HashIndex {}));
}

#[component]
fn RootIndex() -> Element {
rsx! { h2 { "Root Index" } }
}

#[component]
fn Fixed() -> Element {
rsx! {
h2 { "Fixed" }
Outlet::<Route> { }
}
}

#[component]
fn FixedIndex() -> Element {
rsx! { h3 { "Fixed - Index" } }
}

#[component]
fn FixedFixed() -> Element {
rsx! { h3 { "Fixed - Fixed"} }
}

#[component]
fn Parameter(id: u8) -> Element {
rsx! {
h2 { "Parameter {id}" }
Outlet::<Route> { }
}
}

#[component]
fn ParameterIndex(id: u8) -> Element {
rsx! { h3 { "Parameter - Index" } }
}

#[component]
fn ParameterFixed(id: u8) -> Element {
rsx! { h3 { "Parameter - Fixed" } }
}

#[component]
fn HashQuery(id: u8, query: String, hash: String) -> Element {
rsx! {
h2 { "Hash Query" }
h3 { "id: {id}" }
h3 { "query: {query}" }
h3 { "hash: {hash}" }
}
}

#[component]
fn HashIndex() -> Element {
rsx! { h3 { "Hash Index" } }
}

#[component]
fn HashId(id: u8, query: String) -> Element {
rsx! {
h3 { "Hash Id {id}" }
h3 { "query: {query}" }
}
}

0 comments on commit 3798f96

Please sign in to comment.