mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 09:54:41 -05:00
fix: correctly handle optional parameters in ParentRoute (#3784)
This commit is contained in:
@@ -128,6 +128,8 @@ pub trait MatchNestedRoutes {
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_;
|
||||
|
||||
fn optional(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
|
||||
@@ -17,6 +17,7 @@ pub struct AnyNestedRoute {
|
||||
)
|
||||
-> (Option<(RouteMatchId, AnyNestedMatch)>, &'a str),
|
||||
generate_routes: fn(&Erased) -> Vec<GeneratedRouteData>,
|
||||
optional: fn(&Erased) -> bool,
|
||||
}
|
||||
|
||||
impl Clone for AnyNestedRoute {
|
||||
@@ -74,11 +75,18 @@ where
|
||||
value.get_ref::<T>().generate_routes().into_iter().collect()
|
||||
}
|
||||
|
||||
fn optional<T: MatchNestedRoutes + Send + Clone + 'static>(
|
||||
value: &Erased,
|
||||
) -> bool {
|
||||
value.get_ref::<T>().optional()
|
||||
}
|
||||
|
||||
AnyNestedRoute {
|
||||
value: Erased::new(self),
|
||||
clone: clone::<T>,
|
||||
match_nested: match_nested::<T>,
|
||||
generate_routes: generate_routes::<T>,
|
||||
optional: optional::<T>,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,4 +105,8 @@ impl MatchNestedRoutes for AnyNestedRoute {
|
||||
fn generate_routes(&self) -> impl IntoIterator<Item = GeneratedRouteData> {
|
||||
(self.generate_routes)(&self.value)
|
||||
}
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
(self.optional)(&self.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +196,19 @@ where
|
||||
type Data = Data;
|
||||
type Match = NestedMatch<Children::Match, View>;
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
self.segments.optional()
|
||||
&& self.children.as_ref().map(|n| n.optional()).unwrap_or(true)
|
||||
}
|
||||
|
||||
fn match_nested<'a>(
|
||||
&'a self,
|
||||
path: &'a str,
|
||||
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||
// if this was optional (for example, this whole nested route definition consisted of an optional param),
|
||||
// then we'll need to retest the inner value against the starting path, if this one succeeds and the inner one fails
|
||||
let this_was_optional = self.segments.optional();
|
||||
|
||||
self.segments
|
||||
.test(path)
|
||||
.and_then(
|
||||
@@ -208,15 +217,49 @@ where
|
||||
mut params,
|
||||
matched,
|
||||
}| {
|
||||
let (_, inner, remaining) = match &self.children {
|
||||
None => (None, None, remaining),
|
||||
Some(children) => {
|
||||
let (inner, remaining) =
|
||||
children.match_nested(remaining);
|
||||
let (id, inner) = inner?;
|
||||
(Some(id), Some(inner), remaining)
|
||||
}
|
||||
};
|
||||
let (_, inner, remaining, was_optional_fallback) =
|
||||
match &self.children {
|
||||
None => (None, None, remaining, false),
|
||||
Some(children) => {
|
||||
let (inner, remaining) =
|
||||
children.match_nested(remaining);
|
||||
|
||||
match inner {
|
||||
Some((id, inner)) => (
|
||||
Some(id),
|
||||
Some(inner),
|
||||
remaining,
|
||||
false,
|
||||
),
|
||||
None if this_was_optional => {
|
||||
// if the parent route was optional, re-match children against full path
|
||||
let (inner, remaining) =
|
||||
children.match_nested(path);
|
||||
let (id, inner) = inner?;
|
||||
(Some(id), Some(inner), remaining, true)
|
||||
}
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// if this was an optional route, re-parse its params
|
||||
if was_optional_fallback {
|
||||
// new params are based on the path it matched (up to the point where the matched child begins)
|
||||
// e.g., if we have /:foo?/bar, for /bar we should *not* have { "foo": "bar" }
|
||||
// so, we re-parse based on "" to yield { "foo": "" }
|
||||
let matched = inner
|
||||
.as_ref()
|
||||
.map(|inner| inner.as_matched())
|
||||
.unwrap_or("");
|
||||
let rematch = path
|
||||
.trim_end_matches(&format!("{matched}{remaining}"));
|
||||
let new_partial = self.segments.test(rematch).unwrap();
|
||||
params = new_partial.params;
|
||||
}
|
||||
|
||||
let inner_params = inner
|
||||
.as_ref()
|
||||
.map(|inner| inner.to_params())
|
||||
|
||||
@@ -31,6 +31,10 @@ impl MatchNestedRoutes for () {
|
||||
type Data = ();
|
||||
type Match = ();
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn match_nested<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
@@ -95,6 +99,10 @@ where
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
self.0.generate_routes()
|
||||
}
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
self.0.optional()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> MatchParams for Either<A, B>
|
||||
@@ -180,6 +188,10 @@ where
|
||||
|
||||
A.chain(B)
|
||||
}
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
self.0.optional() && self.1.optional()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MatchNestedRoutes for StaticVec<T>
|
||||
@@ -206,6 +218,10 @@ where
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
self.iter().flat_map(T::generate_routes)
|
||||
}
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
self.iter().all(|n| n.optional())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! chain_generated {
|
||||
@@ -273,6 +289,13 @@ macro_rules! tuples {
|
||||
type Data = ($($ty::Data,)*);
|
||||
type Match = $either<$($ty::Match,)*>;
|
||||
|
||||
fn optional(&self) -> bool {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = &self;
|
||||
$($ty.optional() &&)*
|
||||
true
|
||||
}
|
||||
|
||||
fn match_nested<'a>(&'a self, path: &'a str) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user