abstract interface to walk nested routes and to access views

This commit is contained in:
Greg Johnston 2024-03-02 20:24:13 -05:00
parent ca54762806
commit b46dffb729
3 changed files with 140 additions and 69 deletions

View file

@ -36,7 +36,7 @@ impl<'a, Children> Routes<Children>
where
Children: MatchNestedRoutes<'a>,
{
pub fn match_route(&self, path: &'a str) -> Option<Children::Match> {
pub fn match_route(&'a self, path: &'a str) -> Option<Children::Match> {
let path = match &self.base {
None => path,
Some(base) => {
@ -72,18 +72,23 @@ where
}
}
pub trait IntoParams<'a> {
type IntoParams: IntoIterator<Item = (&'a str, &'a str)>;
pub trait MatchInterface<'a> {
type Params: IntoIterator<Item = (&'a str, &'a str)>;
type Child;
type View;
fn to_params(&self) -> Self::IntoParams;
fn to_params(&self) -> Self::Params;
fn to_child(&'a self) -> Self::Child;
fn to_view(&self) -> Self::View;
}
pub trait MatchNestedRoutes<'a> {
type Data;
//type ParamsIter: IntoIterator<Item = (&'a str, &'a str)> + Clone;
type Match: IntoParams<'a>;
type Match: MatchInterface<'a>;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str);
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str);
fn generate_routes(
&self,
@ -94,7 +99,7 @@ pub trait MatchNestedRoutes<'a> {
mod tests {
use super::{NestedRoute, ParamSegment, Routes};
use crate::{
matching::{IntoParams, StaticSegment, WildcardSegment},
matching::{MatchInterface, StaticSegment, WildcardSegment},
PathSegment,
};
@ -104,7 +109,7 @@ mod tests {
segments: StaticSegment("/"),
children: (),
data: (),
view: (),
view: || (),
});
let matched = routes.match_route("/");
assert!(matched.is_some());
@ -129,9 +134,8 @@ mod tests {
data: (),
view: "Home",
});
let matched = routes.match_route("/author/contact").unwrap();
assert_eq!(matched.matched(), "");
assert_eq!(matched.child().matched(), "/author/contact");
// route generation
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
@ -143,6 +147,14 @@ mod tests {
PathSegment::Static("contact".into())
]]
);
let matched = routes.match_route("/author/contact").unwrap();
assert_eq!(matched.matched(), "");
assert_eq!(matched.to_child().matched(), "/author/contact");
let view = matched.to_view();
assert_eq!(*view, "Home");
assert_eq!(*matched.to_child().to_view(), "Contact Me");
}
#[test]
@ -172,17 +184,17 @@ mod tests {
segments: StaticSegment(""),
children: (),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: (),
view: || (),
},
),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
@ -191,17 +203,17 @@ mod tests {
segments: StaticSegment(""),
children: (),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: (StaticSegment("post"), ParamSegment("id")),
children: (),
data: (),
view: (),
view: || (),
},
),
data: (),
view: (),
view: || (),
},
));
@ -254,17 +266,17 @@ mod tests {
segments: StaticSegment("/"),
children: (),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: (),
view: || (),
},
),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
@ -273,13 +285,13 @@ mod tests {
segments: StaticSegment(""),
children: (),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("category"),
children: (),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: (
@ -288,11 +300,11 @@ mod tests {
),
children: (),
data: (),
view: (),
view: || (),
},
),
data: (),
view: (),
view: || (),
},
NestedRoute {
segments: (
@ -301,7 +313,7 @@ mod tests {
),
children: (),
data: (),
view: (),
view: || (),
},
),
"/portfolio",

View file

@ -1,5 +1,5 @@
use super::{
IntoParams, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
};
use crate::PathSegment;
use core::iter;
@ -7,61 +7,71 @@ use core::iter;
mod tuples;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NestedRoute<Segments, Children, Data, ViewFn> {
pub struct NestedRoute<Segments, Children, Data, View> {
pub segments: Segments,
pub children: Children,
pub data: Data,
pub view: ViewFn,
pub view: View,
}
#[derive(Debug, PartialEq, Eq)]
pub struct NestedMatch<'a, ParamsIter, Child> {
pub struct NestedMatch<'a, ParamsIter, Child, View> {
/// The portion of the full path matched only by this nested route.
matched: &'a str,
/// The map of params matched only by this nested route.
params: ParamsIter,
/// The nested route.
child: Child,
view: &'a View,
}
impl<'a, ParamsIter, Child> IntoParams<'a>
for NestedMatch<'a, ParamsIter, Child>
impl<'a, ParamsIter, Child, View> MatchInterface<'a>
for NestedMatch<'a, ParamsIter, Child, View>
where
ParamsIter: IntoIterator<Item = (&'a str, &'a str)> + Clone,
Child: 'a,
{
type IntoParams = ParamsIter;
type Params = ParamsIter;
type Child = &'a Child;
type View = &'a View;
fn to_params(&self) -> Self::IntoParams {
fn to_params(&self) -> Self::Params {
self.params.clone()
}
fn to_child(&'a self) -> Self::Child {
&self.child
}
fn to_view(&self) -> Self::View {
self.view
}
}
impl<'a, ParamsIter, Child> NestedMatch<'a, ParamsIter, Child> {
impl<'a, ParamsIter, Child, View> NestedMatch<'a, ParamsIter, Child, View> {
pub fn matched(&self) -> &'a str {
self.matched
}
pub fn child(&self) -> &Child {
&self.child
}
}
impl<'a, Segments, Children, Data, ViewFn> MatchNestedRoutes<'a>
for NestedRoute<Segments, Children, Data, ViewFn>
impl<'a, Segments, Children, Data, View> MatchNestedRoutes<'a>
for NestedRoute<Segments, Children, Data, View>
where
Segments: PossibleRouteMatch,
Children: MatchNestedRoutes<'a>,
<Segments::ParamsIter<'a> as IntoIterator>::IntoIter: Clone,
<<Children::Match as IntoParams<'a>>::IntoParams as IntoIterator>::IntoIter:
<<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter:
Clone,
Children: 'a,
View: 'a,
{
type Data = Data;
type Match = NestedMatch<'a, iter::Chain<
<Segments::ParamsIter<'a> as IntoIterator>::IntoIter,
<<Children::Match as IntoParams<'a>>::IntoParams as IntoIterator>::IntoIter,
>, Children::Match>;
<<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter,
>, Children::Match, View>;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
self.segments
.test(path)
.and_then(
@ -81,6 +91,7 @@ where
matched,
params: params.chain(inner.to_params()),
child: inner,
view: &self.view,
}),
remaining,
))

View file

@ -1,21 +1,26 @@
use crate::{
matching::{IntoParams, MatchNestedRoutes},
matching::{MatchInterface, MatchNestedRoutes},
PathSegment,
};
use core::iter;
use either_of::*;
impl<'a> IntoParams<'a> for () {
type IntoParams = iter::Empty<(&'a str, &'a str)>;
impl<'a> MatchInterface<'a> for () {
type Params = iter::Empty<(&'a str, &'a str)>;
type Child = ();
type View = ();
fn to_params(&self) -> Self::IntoParams {
fn to_params(&self) -> Self::Params {
iter::empty()
}
fn to_child(&self) -> Self::Child {}
fn to_view(&self) -> Self::View {}
}
impl<'a> MatchNestedRoutes<'a> for () {
type Data = ();
//type ParamsIter = iter::Empty<(&'a str, &'a str)>;
type Match = ();
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
@ -29,15 +34,25 @@ impl<'a> MatchNestedRoutes<'a> for () {
}
}
impl<'a, A> IntoParams<'a> for (A,)
impl<'a, A> MatchInterface<'a> for (A,)
where
A: IntoParams<'a>,
A: MatchInterface<'a>,
{
type IntoParams = A::IntoParams;
type Params = A::Params;
type Child = A::Child;
type View = A::View;
fn to_params(&self) -> Self::IntoParams {
fn to_params(&self) -> Self::Params {
self.0.to_params()
}
fn to_child(&'a self) -> Self::Child {
self.0.to_child()
}
fn to_view(&self) -> Self::View {
self.0.to_view()
}
}
impl<'a, A> MatchNestedRoutes<'a> for (A,)
@ -47,7 +62,7 @@ where
type Data = A::Data;
type Match = A::Match;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
self.0.match_nested(path)
}
@ -58,22 +73,38 @@ where
}
}
impl<'a, A, B> IntoParams<'a> for Either<A, B>
impl<'a, A, B> MatchInterface<'a> for Either<A, B>
where
A: IntoParams<'a>,
B: IntoParams<'a>,
A: MatchInterface<'a>,
B: MatchInterface<'a>,
{
type IntoParams = Either<
<A::IntoParams as IntoIterator>::IntoIter,
<B::IntoParams as IntoIterator>::IntoIter,
type Params = Either<
<A::Params as IntoIterator>::IntoIter,
<B::Params as IntoIterator>::IntoIter,
>;
type Child = Either<A::Child, B::Child>;
type View = Either<A::View, B::View>;
fn to_params(&self) -> Self::IntoParams {
fn to_params(&self) -> Self::Params {
match self {
Either::Left(i) => Either::Left(i.to_params().into_iter()),
Either::Right(i) => Either::Right(i.to_params().into_iter()),
}
}
fn to_child(&'a self) -> Self::Child {
match self {
Either::Left(i) => Either::Left(i.to_child()),
Either::Right(i) => Either::Right(i.to_child()),
}
}
fn to_view(&self) -> Self::View {
match self {
Either::Left(i) => Either::Left(i.to_view()),
Either::Right(i) => Either::Right(i.to_view()),
}
}
}
impl<'a, A, B> MatchNestedRoutes<'a> for (A, B)
@ -84,7 +115,7 @@ where
type Data = (A::Data, B::Data);
type Match = Either<A::Match, B::Match>;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
#[allow(non_snake_case)]
let (A, B) = &self;
if let (Some(matched), remaining) = A.match_nested(path) {
@ -124,29 +155,46 @@ macro_rules! chain_generated {
macro_rules! tuples {
($either:ident => $($ty:ident),*) => {
impl<'a, $($ty,)*> IntoParams<'a> for $either <$($ty,)*>
impl<'a, $($ty,)*> MatchInterface<'a> for $either <$($ty,)*>
where
$($ty: IntoParams<'a>),*,
$($ty: MatchInterface<'a>),*,
$($ty::Child: 'a),*,
$($ty::View: 'a),*,
{
type IntoParams = $either<$(
<$ty::IntoParams as IntoIterator>::IntoIter,
type Params = $either<$(
<$ty::Params as IntoIterator>::IntoIter,
)*>;
type Child = $either<$($ty::Child,)*>;
type View = $either<$($ty::View,)*>;
fn to_params(&self) -> Self::IntoParams {
fn to_params(&self) -> Self::Params {
match self {
$($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)*
}
}
fn to_child(&'a self) -> Self::Child {
match self {
$($either::$ty(i) => $either::$ty(i.to_child()),)*
}
}
fn to_view(&self) -> Self::View {
match self {
$($either::$ty(i) => $either::$ty(i.to_view()),)*
}
}
}
impl<'a, $($ty),*> MatchNestedRoutes<'a> for ($($ty,)*)
where
$($ty: MatchNestedRoutes<'a>),*,
$($ty::Match: 'a),*,
{
type Data = ($($ty::Data,)*);
type Match = $either<$($ty::Match,)*>;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
#[allow(non_snake_case)]
let ($($ty,)*) = &self;