use super::{
IntoChooseViewMaybeErased, MatchInterface, MatchNestedRoutes, PathSegment,
PossibleRouteMatch, RouteMatchId,
};
use crate::{ChooseView, GeneratedRouteData, MatchParams, Method, SsrMode};
use core::{fmt, iter};
use either_of::Either;
use std::{
borrow::Cow,
collections::HashSet,
sync::atomic::{AtomicU16, Ordering},
};
use tachys::prelude::IntoMaybeErased;
pub mod any_nested_match;
pub mod any_nested_route;
mod tuples;
pub(crate) static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
#[derive(Debug, PartialEq, Eq)]
pub struct NestedRoute<Segments, Children, Data, View> {
id: u16,
segments: Segments,
children: Option<Children>,
data: Data,
view: View,
methods: HashSet<Method>,
ssr_mode: SsrMode,
}
impl<Segments, Children, Data, View> IntoMaybeErased
for NestedRoute<Segments, Children, Data, View>
where
Self: MatchNestedRoutes + Send + Clone + 'static,
{
#[cfg(erase_components)]
type Output = any_nested_route::AnyNestedRoute;
#[cfg(not(erase_components))]
type Output = Self;
fn into_maybe_erased(self) -> Self::Output {
#[cfg(erase_components)]
{
use any_nested_route::IntoAnyNestedRoute;
self.into_any_nested_route()
}
#[cfg(not(erase_components))]
{
self
}
}
}
impl<Segments, Children, Data, View> Clone
for NestedRoute<Segments, Children, Data, View>
where
Segments: Clone,
Children: Clone,
Data: Clone,
View: Clone,
{
fn clone(&self) -> Self {
Self {
id: self.id,
segments: self.segments.clone(),
children: self.children.clone(),
data: self.data.clone(),
view: self.view.clone(),
methods: self.methods.clone(),
ssr_mode: self.ssr_mode.clone(),
}
}
}
impl<Segments, View> NestedRoute<Segments, (), (), View> {
pub fn new(
path: Segments,
view: View,
) -> NestedRoute<
Segments,
(),
(),
<View as IntoChooseViewMaybeErased>::Output,
>
where
View: ChooseView,
{
NestedRoute {
id: ROUTE_ID.fetch_add(1, Ordering::Relaxed),
segments: path,
children: None,
data: (),
view: view.into_maybe_erased(),
methods: [Method::Get].into(),
ssr_mode: Default::default(),
}
}
}
impl<Segments, Data, View> NestedRoute<Segments, (), Data, View> {
pub fn child<Children>(
self,
child: Children,
) -> NestedRoute<Segments, Children, Data, View> {
let Self {
id,
segments,
data,
view,
ssr_mode,
methods,
..
} = self;
NestedRoute {
id,
segments,
children: Some(child),
data,
view,
ssr_mode,
methods,
}
}
pub fn ssr_mode(mut self, ssr_mode: SsrMode) -> Self {
self.ssr_mode = ssr_mode;
self
}
}
#[derive(PartialEq, Eq)]
pub struct NestedMatch<Child, View> {
id: RouteMatchId,
matched: String,
params: Vec<(Cow<'static, str>, String)>,
child: Option<Child>,
view_fn: View,
}
impl<Child, View> fmt::Debug for NestedMatch<Child, View>
where
Child: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NestedMatch")
.field("matched", &self.matched)
.field("params", &self.params)
.field("child", &self.child)
.finish()
}
}
impl<Child, View> MatchParams for NestedMatch<Child, View> {
#[inline(always)]
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
self.params.clone()
}
}
impl<Child, View> MatchInterface for NestedMatch<Child, View>
where
Child: MatchInterface + MatchParams + 'static,
View: ChooseView,
{
type Child = Child;
fn as_id(&self) -> RouteMatchId {
self.id
}
fn as_matched(&self) -> &str {
&self.matched
}
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
(self.view_fn, self.child)
}
}
impl<Segments, Children, Data, View> MatchNestedRoutes
for NestedRoute<Segments, Children, Data, View>
where
Self: 'static,
Segments: PossibleRouteMatch,
Children: MatchNestedRoutes,
View: ChooseView,
{
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) {
let this_was_optional = self.segments.optional();
self.segments
.test(path)
.and_then({
type Params = Vec<(Cow<'static, str>, String)>;
fn inner<'a, Children>(
this_was_optional: bool,
path: &'a str,
remaining: &'a str,
segments: &dyn PossibleRouteMatch,
children: &'a Option<Children>,
mut params: Params,
) -> Option<(Option<Children::Match>, &'a str, Params)>
where
Children: MatchNestedRoutes,
{
let mut was_optional_fallback = false;
let (child, remaining) = match children {
None => (None, remaining),
Some(children) => {
let (inner, remaining) =
children.match_nested(remaining);
if let Some((_, child)) = inner {
(Some(child), remaining)
} else if this_was_optional {
was_optional_fallback = true;
let (inner, remaining) =
children.match_nested(path);
inner.map(|(_, child)| {
(Some(child), remaining)
})?
} else {
return None;
}
}
};
if remaining.is_empty() || remaining == "/" {
if was_optional_fallback {
let matched = child
.as_ref()
.map_or("", Children::Match::as_matched);
let rematch = path.trim_end_matches(&format!(
"{matched}{remaining}"
));
let new_partial = segments.test(rematch).unwrap();
params = new_partial.params;
}
params.extend(
child
.as_ref()
.map_or(Vec::new(), Children::Match::to_params),
);
Some((child, remaining, params))
} else {
None
}
}
|partial_match| {
let (child, remaining, params) = inner(
this_was_optional,
path,
partial_match.remaining,
&self.segments,
&self.children,
partial_match.params,
)?;
let id = RouteMatchId(self.id);
Some((
Some((
id,
NestedMatch {
id,
matched: partial_match.matched.to_string(),
params,
child,
view_fn: self.view.clone(),
},
)),
remaining,
))
}
})
.unwrap_or((None, path))
}
fn generate_routes(
&self,
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
let mut segment_routes = Vec::new();
self.segments.generate_path(&mut segment_routes);
let children = self.children.as_ref();
let ssr_mode = self.ssr_mode.clone();
let methods = self.methods.clone();
let regenerate = match &ssr_mode {
SsrMode::Static(data) => match data.regenerate.as_ref() {
None => vec![],
Some(regenerate) => vec![regenerate.clone()],
},
_ => vec![],
};
match children {
None => Either::Left(iter::once(GeneratedRouteData {
segments: segment_routes,
ssr_mode,
methods,
regenerate,
})),
Some(children) => {
Either::Right(children.generate_routes().into_iter().map(
move |child| {
let segments = segment_routes
.clone()
.into_iter()
.chain(child.segments)
.collect();
let mut methods = methods.clone();
methods.extend(child.methods);
let mut regenerate = regenerate.clone();
regenerate.extend(child.regenerate);
if child.ssr_mode > ssr_mode {
GeneratedRouteData {
segments,
ssr_mode: child.ssr_mode,
methods,
regenerate,
}
} else {
GeneratedRouteData {
segments,
ssr_mode: ssr_mode.clone(),
methods,
regenerate,
}
}
},
))
}
}
}
}