work on routing utils

This commit is contained in:
Greg Johnston 2024-02-28 21:59:20 -05:00
parent 4d3fb37b35
commit 21e53042e8
6 changed files with 207 additions and 130 deletions

View file

@ -15,9 +15,14 @@ pub use static_segment::*;
/// as subsequent segments of the URL and tries to match them all. For a "vertical"
/// matching that sees a tuple as alternatives to one another, see [`RouteChild`](super::RouteChild).
pub trait PossibleRouteMatch {
type ParamsIter<'a>: IntoIterator<Item = (&'a str, &'a str)>;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>;
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
fn test<'a>(
&self,
path: &'a str,
) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>>;
fn generate_path(&self, path: &mut Vec<PathSegment>);
}

View file

@ -1,11 +1,14 @@
use super::{PartialPathMatch, PossibleRouteMatch};
use crate::PathSegment;
use alloc::vec::Vec;
use core::iter;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ParamSegment(pub &'static str);
impl PossibleRouteMatch for ParamSegment {
type ParamsIter<'a> = iter::Once<(&'a str, &'a str)>;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
let mut matched_len = 0;
let mut test = path.chars().peekable();
@ -24,7 +27,10 @@ impl PossibleRouteMatch for ParamSegment {
Some(&path[0..matched_len])
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
fn test<'a>(
&self,
path: &'a str,
) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>> {
let mut matched_len = 0;
let mut param_offset = 0;
let mut param_len = 0;
@ -49,7 +55,7 @@ impl PossibleRouteMatch for ParamSegment {
let (matched, remaining) = path.split_at(matched_len);
let param_value =
vec![(self.0, &path[param_offset..param_len + param_offset])];
iter::once((self.0, &path[param_offset..param_len + param_offset]));
Some(PartialPathMatch::new(remaining, param_value, matched))
}
@ -62,11 +68,16 @@ impl PossibleRouteMatch for ParamSegment {
pub struct WildcardSegment(pub &'static str);
impl PossibleRouteMatch for WildcardSegment {
type ParamsIter<'a> = iter::Once<(&'a str, &'a str)>;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
Some(path)
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
fn test<'a>(
&self,
path: &'a str,
) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>> {
let mut matched_len = 0;
let mut param_offset = 0;
let mut param_len = 0;
@ -84,7 +95,7 @@ impl PossibleRouteMatch for WildcardSegment {
let (matched, remaining) = path.split_at(matched_len);
let param_value =
vec![(self.0, &path[param_offset..param_len + param_offset])];
iter::once((self.0, &path[param_offset..param_len + param_offset]));
Some(PartialPathMatch::new(remaining, param_value, matched))
}
@ -106,7 +117,8 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "");
assert_eq!(matched.params()[0], ("a", "foo"));
let params = matched.params().collect::<Vec<_>>();
assert_eq!(params[0], ("a", "foo"));
}
#[test]
@ -117,7 +129,8 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "/");
assert_eq!(matched.params()[0], ("a", "foo"));
let params = matched.params().collect::<Vec<_>>();
assert_eq!(params[0], ("a", "foo"));
}
#[test]
@ -128,8 +141,9 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
assert_eq!(matched.params()[0], ("a", "foo"));
assert_eq!(matched.params()[1], ("b", "bar"));
let params = matched.params().collect::<Vec<_>>();
assert_eq!(params[0], ("a", "foo"));
assert_eq!(params[1], ("b", "bar"));
}
#[test]
@ -144,6 +158,7 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar/////");
assert_eq!(matched.remaining(), "");
assert_eq!(matched.params()[0], ("rest", "////"));
let params = matched.params().collect::<Vec<_>>();
assert_eq!(params[0], ("rest", "////"));
}
}

View file

@ -1,14 +1,20 @@
use super::{PartialPathMatch, PossibleRouteMatch};
use crate::PathSegment;
use alloc::vec::Vec;
use core::iter;
impl PossibleRouteMatch for () {
type ParamsIter<'a> = iter::Empty<(&'a str, &'a str)>;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
Some(path)
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
Some(PartialPathMatch::new(path, [], ""))
fn test<'a>(
&self,
path: &'a str,
) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>> {
Some(PartialPathMatch::new(path, iter::empty(), ""))
}
fn generate_path(&self, _path: &mut Vec<PathSegment>) {}
@ -18,6 +24,8 @@ impl PossibleRouteMatch for () {
pub struct StaticSegment(pub &'static str);
impl PossibleRouteMatch for StaticSegment {
type ParamsIter<'a> = iter::Empty<(&'a str, &'a str)>;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
let mut matched_len = 0;
let mut this = self.0.chars();
@ -46,12 +54,14 @@ impl PossibleRouteMatch for StaticSegment {
}
}
}
println!("matching on {self:?}, has_matched = {has_matched}");
has_matched.then(|| &path[matched_len..])
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
fn test<'a>(
&self,
path: &'a str,
) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>> {
let mut matched_len = 0;
let mut test = path.chars();
let mut this = self.0.chars();
@ -86,7 +96,8 @@ impl PossibleRouteMatch for StaticSegment {
// the remaining is built from the path in, with the slice moved
// by the length of this match
let (matched, remaining) = path.split_at(matched_len);
has_matched.then(|| PartialPathMatch::new(remaining, [], matched))
has_matched
.then(|| PartialPathMatch::new(remaining, iter::empty(), matched))
}
fn generate_path(&self, path: &mut Vec<PathSegment>) {
@ -105,7 +116,8 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "");
assert!(matched.params().is_empty());
let params = matched.params().collect::<Vec<_>>();
assert!(params.is_empty());
}
#[test]
@ -123,7 +135,8 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo");
assert_eq!(matched.remaining(), "/");
assert!(matched.params().is_empty());
let params = matched.params().collect::<Vec<_>>();
assert!(params.is_empty());
}
#[test]
@ -134,7 +147,8 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
assert!(matched.params().is_empty());
let params = matched.params().collect::<Vec<_>>();
assert!(params.is_empty());
}
#[test]
@ -160,6 +174,7 @@ mod tests {
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
assert!(matched.params().is_empty());
let params = matched.params().collect::<Vec<_>>();
assert!(params.is_empty());
}
}

View file

@ -1,27 +1,54 @@
use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
use alloc::{string::String, vec::Vec};
use core::iter::Chain;
macro_rules! chain_types {
($first:ty, $second:ty, ) => {
Chain<
$first,
<<$second as PossibleRouteMatch>::ParamsIter<'a> as IntoIterator>::IntoIter
>
};
($first:ty, $second:ty, $($rest:ty,)+) => {
chain_types!(
Chain<
$first,
<<$second as PossibleRouteMatch>::ParamsIter<'a> as IntoIterator>::IntoIter,
>,
$($rest,)+
)
}
}
macro_rules! tuples {
($($ty:ident),*) => {
impl<$($ty),*> PossibleRouteMatch for ($($ty,)*)
($first:ident => $($ty:ident),*) => {
impl<$first, $($ty),*> PossibleRouteMatch for ($first, $($ty,)*)
where
$first: PossibleRouteMatch,
$($ty: PossibleRouteMatch),*,
{
type ParamsIter<'a> = chain_types!(<<$first>::ParamsIter<'a> as IntoIterator>::IntoIter, $($ty,)*);
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>
{
#[allow(non_snake_case)]
let ($($ty,)*) = &self;
let ($first, $($ty,)*) = &self;
let path = $first.matches(path)?;
$(let path = $ty.matches(path)?;)*
Some(path)
}
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>
{
let mut full_params = Vec::new();
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a, Self::ParamsIter<'a>>> {
let mut matched_len = 0;
#[allow(non_snake_case)]
let ($($ty,)*) = &self;
let ($first, $($ty,)*) = &self;
let remaining = path;
let PartialPathMatch {
remaining,
matched,
params
} = $first.test(remaining)?;
matched_len += matched.len();
let params_iter = params.into_iter();
$(
let PartialPathMatch {
remaining,
@ -29,18 +56,19 @@ macro_rules! tuples {
params
} = $ty.test(remaining)?;
matched_len += matched.len();
full_params.extend(params);
let params_iter = params_iter.chain(params);
)*
Some(PartialPathMatch {
remaining,
matched: &path[0..matched_len],
params: full_params
params: params_iter
})
}
fn generate_path(&self, path: &mut Vec<PathSegment>) {
#[allow(non_snake_case)]
let ($($ty,)*) = &self;
let ($first, $($ty,)*) = &self;
$first.generate_path(path);
$(
$ty.generate_path(path);
)*
@ -49,33 +77,33 @@ macro_rules! tuples {
};
}
tuples!(A, B);
tuples!(A, B, C);
tuples!(A, B, C, D);
tuples!(A, B, C, D, E);
tuples!(A, B, C, D, E, F);
tuples!(A, B, C, D, E, F, G);
tuples!(A, B, C, D, E, F, G, H);
tuples!(A, B, C, D, E, F, G, H, I);
tuples!(A, B, C, D, E, F, G, H, I, J);
tuples!(A, B, C, D, E, F, G, H, I, J, K);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
tuples!(
tuples!(A => B);
tuples!(A => B, C);
tuples!(A => B, C, D);
tuples!(A => B, C, D, E);
tuples!(A => B, C, D, E, F);
tuples!(A => B, C, D, E, F, G);
tuples!(A => B, C, D, E, F, G, H);
tuples!(A => B, C, D, E, F, G, H, I);
tuples!(A => B, C, D, E, F, G, H, I, J);
tuples!(A => B, C, D, E, F, G, H, I, J, K);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
tuples!(A => B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
/*tuples!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
);
tuples!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y,
Z
);
);*/

View file

@ -1,8 +1,8 @@
mod horizontal;
mod vertical;
use alloc::{borrow::Cow, vec::Vec};
use core::iter;
pub use horizontal::*;
use std::fmt::Debug;
pub use vertical::*;
pub struct Routes<Children> {
@ -29,14 +29,11 @@ impl<Children> Routes<Children> {
}
}
impl<Children> Routes<Children>
impl<'a, Children> Routes<Children>
where
Children: MatchNestedRoutes,
Children: MatchNestedRoutes<'a>,
{
pub fn match_route<'a>(
&self,
path: &'a str,
) -> Option<Children::Match<'a>> {
pub fn match_route(&self, path: &'a str) -> Option<Children::Match> {
let path = match &self.base {
None => path,
Some(base) if base.starts_with('/') => {
@ -49,7 +46,6 @@ where
let (matched, remaining) = self.children.match_nested(path);
let matched = matched?;
println!("remaining = {remaining:?}");
if !remaining.is_empty() {
None
@ -59,38 +55,50 @@ where
}
}
pub trait IntoParams<'a> {
type IntoParams: IntoIterator<Item = (&'a str, &'a str)>;
fn to_params(&self) -> Self::IntoParams;
}
pub trait MatchNestedRoutes<'a> {
type Data;
type ParamsIter: IntoIterator<Item = (&'a str, &'a str)> + Clone;
type Match: IntoParams<'a>;
const DEPTH: usize;
fn matches(&self, path: &'a str) -> Option<&'a str>;
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str);
}
#[derive(Debug, PartialEq, Eq)]
pub struct NestedMatch<'a, Child> {
pub struct NestedMatch<'a, ParamsIter, Child> {
/// 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: Vec<(&'a str, &'a str)>,
params: ParamsIter,
/// The nested route.
child: Child,
}
impl<'a, Child> NestedMatch<'a, Child> {
pub fn matched(&self) -> &'a str {
self.matched
}
impl<'a, ParamsIter, Child> IntoParams<'a>
for NestedMatch<'a, ParamsIter, Child>
where
ParamsIter: IntoIterator<Item = (&'a str, &'a str)> + Clone,
{
type IntoParams = ParamsIter;
pub fn matched_params(&self) -> &[(&'a str, &'a str)] {
&self.params
fn to_params(&self) -> Self::IntoParams {
self.params.clone()
}
}
pub trait MatchNestedRoutes {
type Data;
type Match<'a>;
const DEPTH: usize;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>;
fn match_nested<'a>(
&self,
path: &'a str,
) -> (Option<Self::Match<'a>>, &'a str);
impl<'a, ParamsIter, Child> NestedMatch<'a, ParamsIter, Child> {
pub fn matched(&self) -> &'a str {
self.matched
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -101,37 +109,49 @@ pub struct NestedRoute<Segments, Children, Data, View> {
pub view: View,
}
impl MatchNestedRoutes for () {
impl<'a> MatchNestedRoutes<'a> for () {
type Data = ();
type Match<'a> = ();
type ParamsIter = iter::Empty<(&'a str, &'a str)>;
type Match = ();
const DEPTH: usize = 0;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
fn matches(&self, path: &'a str) -> Option<&'a str> {
Some(path)
}
fn match_nested<'a>(
&self,
path: &'a str,
) -> (Option<Self::Match<'a>>, &'a str) {
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
(Some(()), path)
}
}
impl<Segments, Children, Data, View> MatchNestedRoutes
impl<'a> IntoParams<'a> for () {
type IntoParams = iter::Empty<(&'a str, &'a str)>;
fn to_params(&self) -> Self::IntoParams {
iter::empty()
}
}
impl<'a, Segments, Children, Data, View> MatchNestedRoutes<'a>
for NestedRoute<Segments, Children, Data, View>
where
Self: Debug,
Segments: PossibleRouteMatch + Debug,
Children: MatchNestedRoutes,
Segments: PossibleRouteMatch,
Children: MatchNestedRoutes<'a>,
<Segments::ParamsIter<'a> as IntoIterator>::IntoIter: Clone,
<<Children::Match as IntoParams<'a>>::IntoParams as IntoIterator>::IntoIter:
Clone,
{
type Data = Data;
type Match<'a> = NestedMatch<'a, Children::Match<'a>>;
type ParamsIter = iter::Chain<
<Segments::ParamsIter<'a> as IntoIterator>::IntoIter,
<<Children::Match as IntoParams<'a>>::IntoParams as IntoIterator>::IntoIter,
>;
type Match = NestedMatch<'a, Self::ParamsIter, Children::Match>;
const DEPTH: usize = Children::DEPTH;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
fn matches(&self, path: &'a str) -> Option<&'a str> {
if let Some(remaining) = self.segments.matches(path) {
self.children.matches(remaining)
} else {
@ -139,10 +159,7 @@ where
}
}
fn match_nested<'a>(
&self,
path: &'a str,
) -> (Option<Self::Match<'a>>, &'a str) {
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
self.segments
.test(path)
.and_then(
@ -154,11 +171,12 @@ where
let (inner, remaining) =
self.children.match_nested(remaining);
let inner = inner?;
let params = params.into_iter();
Some((
Some(NestedMatch {
matched,
params,
params: params.chain(inner.to_params()),
child: inner,
}),
remaining,
@ -166,35 +184,24 @@ where
},
)
.unwrap_or((None, path))
/*path = remaining;
NestedMatch {
matched_path: matched,
params,
};
return self.children.match_nested_routes(path, matches);
// otherwise, just return the path as the remainder
path*/
}
}
impl<A> MatchNestedRoutes for (A,)
impl<'a, A> MatchNestedRoutes<'a> for (A,)
where
A: MatchNestedRoutes,
A: MatchNestedRoutes<'a>,
{
type Data = A::Data;
type Match<'a> = A::Match<'a>;
type ParamsIter = A::ParamsIter;
type Match = A::Match;
const DEPTH: usize = A::DEPTH;
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
fn matches(&self, path: &'a str) -> Option<&'a str> {
self.0.matches(path)
}
fn match_nested<'a>(
&self,
path: &'a str,
) -> (Option<Self::Match<'a>>, &'a str) {
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
self.0.match_nested(path)
}
}
@ -202,19 +209,21 @@ where
#[cfg(test)]
mod tests {
use super::{NestedRoute, Routes};
use crate::matching::{NestedMatch, StaticSegment};
use crate::matching::StaticSegment;
/* #[test]
#[test]
pub fn matches_single_root_route() {
let routes = Routes::new(NestedRoute {
segments: StaticSegment("/"),
children: (),
data: (),
view: (),
});
let matched = routes.match_route("/");
assert!(matched.is_some());
let matched = routes.match_route("");
assert!(matched.is_some())
}*/
}
#[test]
pub fn matches_nested_route() {
@ -248,7 +257,7 @@ mod tests {
view: "Home",
});
let matched = routes.match_route("/");
assert_eq!(matched, None);
assert!(matched.is_none());
}
/*#[test]
@ -272,21 +281,21 @@ mod tests {
}
#[derive(Debug)]
pub struct PartialPathMatch<'a> {
pub struct PartialPathMatch<'a, ParamsIter> {
pub(crate) remaining: &'a str,
pub(crate) params: Vec<(&'a str, &'a str)>,
pub(crate) params: ParamsIter,
pub(crate) matched: &'a str,
}
impl<'a> PartialPathMatch<'a> {
impl<'a, ParamsIter> PartialPathMatch<'a, ParamsIter> {
pub fn new(
remaining: &'a str,
params: impl Into<Vec<(&'a str, &'a str)>>,
params: ParamsIter,
matched: &'a str,
) -> Self {
Self {
remaining,
params: params.into(),
params,
matched,
}
}
@ -299,8 +308,8 @@ impl<'a> PartialPathMatch<'a> {
self.remaining
}
pub fn params(&self) -> &[(&'a str, &'a str)] {
&self.params
pub fn params(self) -> ParamsIter {
self.params
}
pub fn matched(&self) -> &str {

View file

@ -1,5 +1,10 @@
use super::PartialPathMatch;
pub trait ChooseRoute {
fn choose_route<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
fn choose_route<'a>(
&self,
path: &'a str,
) -> Option<
PartialPathMatch<'a, impl IntoIterator<Item = (&'a str, &'a str)>>,
>;
}