reorganize

This commit is contained in:
Greg Johnston 2024-03-04 09:20:16 -05:00
parent dbd9951a85
commit c3b9932172
15 changed files with 534 additions and 409 deletions

View File

@ -8,5 +8,6 @@ mod static_route;
pub use generate_route_list::*;
pub use method::*;
pub use router::*;
pub use routing_utils::*;
pub use ssr_mode::*;
pub use static_route::*;

View File

@ -3,21 +3,25 @@ use core::marker::PhantomData;
use either_of::Either;
use routing_utils::{
location::Location,
matching::{MatchNestedRoutes, PossibleRouteMatch, Routes},
matching::{MatchInterface, MatchNestedRoutes, PossibleRouteMatch, Routes},
};
use std::borrow::Cow;
use tachys::{
html::attribute::Attribute,
hydration::Cursor,
renderer::Renderer,
ssr::StreamBuilder,
view::{either::EitherState, Position, PositionState, Render, RenderHtml},
view::{
add_attr::AddAnyAttr, either::EitherState, Position, PositionState,
Render, RenderHtml,
},
};
#[derive(Debug)]
pub struct Router<Rndr, Loc, Children, FallbackFn> {
base: Option<Cow<'static, str>>,
location: Loc,
routes: Routes<Children>,
pub routes: Routes<Children>,
fallback: FallbackFn,
rndr: PhantomData<Rndr>,
}
@ -74,21 +78,52 @@ where
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render<Rndr>
trait ChooseView {
type Output;
fn choose(self) -> Self::Output;
}
impl<F, View> ChooseView for F
where
F: Fn() -> View,
{
type Output = View;
fn choose(self) -> Self::Output {
self()
}
}
impl<A, FnA, B, FnB> ChooseView for Either<FnA, FnB>
where
FnA: Fn() -> A,
FnB: Fn() -> B,
{
type Output = Either<A, B>;
fn choose(self) -> Self::Output {
match self {
Either::Left(f) => Either::Left(f()),
Either::Right(f) => Either::Right(f()),
}
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> Render<Rndr>
for Router<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
FallbackFn: Fn() -> Fallback,
Fallback: Render<Rndr>,
for<'a> Children: MatchNestedRoutes<'a>,
for<'a> <Children as MatchNestedRoutes<'a>>::Match: core::fmt::Debug,
for<'a> <<Children as MatchNestedRoutes<'a>>::Match as MatchInterface<'a>>::View:
ChooseView<Output = View>,
View: Render<Rndr>,
Rndr: Renderer,
{
type State = EitherState<
<String as Render<Rndr>>::State,
<Fallback as Render<Rndr>>::State,
Rndr,
>;
type State =
EitherState<View::State, <Fallback as Render<Rndr>>::State, Rndr>;
type FallibleState = ();
fn build(self) -> Self::State {
@ -98,14 +133,31 @@ where
.as_ref()
.map(|url| self.routes.match_route(url.path()))
{
Ok(Some(matched)) => Either::Left(format!("{matched:#?}")),
Ok(Some(matched)) => {
let view = matched.to_view();
let view = view.choose();
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
}
.build()
}
fn rebuild(self, state: &mut Self::State) {
todo!()
let new = match self
.location
.current()
.as_ref()
.map(|url| self.routes.match_route(url.path()))
{
Ok(Some(matched)) => {
let view = matched.to_view();
let view = view.choose();
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
};
new.rebuild(state);
}
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
@ -119,6 +171,69 @@ where
todo!()
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> RenderHtml<Rndr>
for Router<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
FallbackFn: Fn() -> Fallback,
Fallback: RenderHtml<Rndr>,
for<'a> Children: MatchNestedRoutes<'a>,
for<'a> <<Children as MatchNestedRoutes<'a>>::Match as MatchInterface<'a>>::View:
ChooseView<Output = View>,
View: Render<Rndr>,
Rndr: Renderer,
{
// TODO probably pick a max length here
const MIN_LENGTH: usize = Fallback::MIN_LENGTH;
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {
todo!()
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
position: &PositionState,
) -> Self::State {
todo!()
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> AddAnyAttr<Rndr>
for Router<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
FallbackFn: Fn() -> Fallback,
Fallback: Render<Rndr>,
for<'a> Children: MatchNestedRoutes<'a>,
for<'a> <<Children as MatchNestedRoutes<'a>>::Match as MatchInterface<'a>>::View:
ChooseView<Output = View>,
Rndr: Renderer,
Router<Rndr, Loc, Children, FallbackFn>: RenderHtml<Rndr>,
{
type Output<SomeNewAttr: Attribute<Rndr>> = Self;
fn add_any_attr<NewAttr: Attribute<Rndr>>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml<Rndr>,
{
self
}
fn add_any_attr_by_ref<NewAttr: Attribute<Rndr>>(
self,
attr: &NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml<Rndr>,
{
self
}
}
/*
impl<Rndr, Loc, Fal, Children> RenderHtml<Rndr>
for Router<Rndr, Loc, Children, Fal>

View File

@ -85,7 +85,8 @@ impl PossibleRouteMatch for WildcardSegment {
#[cfg(test)]
mod tests {
use super::PossibleRouteMatch;
use crate::matching::{ParamSegment, StaticSegment, WildcardSegment};
use crate::{ParamSegment, StaticSegment, WildcardSegment};
use alloc::vec::Vec;
#[test]
fn single_param_match() {

View File

@ -78,6 +78,7 @@ impl PossibleRouteMatch for StaticSegment {
#[cfg(test)]
mod tests {
use super::{PossibleRouteMatch, StaticSegment};
use alloc::vec::Vec;
#[test]
fn single_static_match() {

View File

@ -1,4 +1,5 @@
use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
use alloc::vec::Vec;
use core::iter::Chain;
macro_rules! chain_types {

View File

@ -1,10 +1,388 @@
//#![no_std]
#![no_std]
#[macro_use]
extern crate alloc;
pub mod location;
pub mod matching;
pub mod params;
mod path_segment;
use alloc::vec::Vec;
pub use path_segment::*;
mod horizontal;
mod nested;
mod vertical;
use crate::PathSegment;
use alloc::borrow::Cow;
pub use horizontal::*;
pub use nested::*;
pub use vertical::*;
#[derive(Debug)]
pub struct Routes<Children> {
base: Option<Cow<'static, str>>,
children: Children,
}
impl<Children> Routes<Children> {
pub fn new(children: Children) -> Self {
Self {
base: None,
children,
}
}
pub fn new_with_base(
children: Children,
base: impl Into<Cow<'static, str>>,
) -> Self {
Self {
base: Some(base.into()),
children,
}
}
}
impl<'a, Children> Routes<Children>
where
Children: MatchNestedRoutes<'a>,
{
pub fn match_route(&'a self, path: &'a str) -> Option<Children::Match> {
let path = match &self.base {
None => path,
Some(base) => {
let (base, path) = if base.starts_with('/') {
(base.trim_start_matches('/'), path.trim_start_matches('/'))
} else {
(base.as_ref(), path)
};
match path.strip_prefix(base) {
Some(path) => path,
None => return None,
}
}
};
let (matched, remaining) = self.children.match_nested(path);
let matched = matched?;
if !remaining.is_empty() {
None
} else {
Some(matched)
}
}
pub fn generate_routes(
&'a self,
) -> (
Option<&str>,
impl IntoIterator<Item = Vec<PathSegment>> + 'a,
) {
(self.base.as_deref(), self.children.generate_routes())
}
}
pub trait MatchInterface<'a> {
type Params: IntoIterator<Item = (&'a str, &'a str)>;
type Child;
type View;
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 Match: MatchInterface<'a>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str);
fn generate_routes(
&self,
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_;
}
#[cfg(test)]
mod tests {
use super::{NestedRoute, ParamSegment, Routes};
use crate::{MatchInterface, PathSegment, StaticSegment, WildcardSegment};
use alloc::vec::Vec;
#[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());
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(paths, vec![vec![PathSegment::Static("/".into())]]);
}
#[test]
pub fn matches_nested_route() {
let routes = Routes::new(NestedRoute {
segments: StaticSegment(""),
children: NestedRoute {
segments: (StaticSegment("author"), StaticSegment("contact")),
children: (),
data: (),
view: "Contact Me",
},
data: (),
view: "Home",
});
// route generation
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(
paths,
vec![vec![
PathSegment::Static("".into()),
PathSegment::Static("author".into()),
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]
pub fn does_not_match_incomplete_route() {
let routes = Routes::new(NestedRoute {
segments: StaticSegment(""),
children: NestedRoute {
segments: (StaticSegment("author"), StaticSegment("contact")),
children: (),
data: (),
view: "Contact Me",
},
data: (),
view: "Home",
});
let matched = routes.match_route("/");
assert!(matched.is_none());
}
#[test]
pub fn chooses_between_nested_routes() {
let routes = Routes::new((
NestedRoute {
segments: StaticSegment("/"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: (StaticSegment("post"), ParamSegment("id")),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
));
// generates routes correctly
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(
paths,
vec![
vec![
PathSegment::Static("/".into()),
PathSegment::Static("".into()),
],
vec![
PathSegment::Static("/".into()),
PathSegment::Static("about".into())
],
vec![
PathSegment::Static("/blog".into()),
PathSegment::Static("".into()),
],
vec![
PathSegment::Static("/blog".into()),
PathSegment::Static("post".into()),
PathSegment::Param("id".into())
]
]
);
let matched = routes.match_route("/about").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/blog").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/blog/post/42").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("id", "42")]);
}
#[test]
pub fn arbitrary_nested_routes() {
let routes = Routes::new_with_base(
(
NestedRoute {
segments: StaticSegment("/"),
children: (
NestedRoute {
segments: StaticSegment("/"),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("category"),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: (
StaticSegment("post"),
ParamSegment("id"),
),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: (
StaticSegment("/contact"),
WildcardSegment("any"),
),
children: (),
data: (),
view: || (),
},
),
"/portfolio",
);
// generates routes correctly
let (base, _paths) = routes.generate_routes();
assert_eq!(base, Some("/portfolio"));
let matched = routes.match_route("/about");
assert!(matched.is_none());
let matched = routes.match_route("/portfolio/about").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/portfolio/blog/post/42").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("id", "42")]);
let matched = routes.match_route("/portfolio/contact").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("any", "")]);
let matched = routes.match_route("/portfolio/contact/foobar").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("any", "foobar")]);
}
}
#[derive(Debug)]
pub struct PartialPathMatch<'a, ParamsIter> {
pub(crate) remaining: &'a str,
pub(crate) params: ParamsIter,
pub(crate) matched: &'a str,
}
impl<'a, ParamsIter> PartialPathMatch<'a, ParamsIter> {
pub fn new(
remaining: &'a str,
params: ParamsIter,
matched: &'a str,
) -> Self {
Self {
remaining,
params,
matched,
}
}
pub fn is_complete(&self) -> bool {
self.remaining.is_empty() || self.remaining == "/"
}
pub fn remaining(&self) -> &str {
self.remaining
}
pub fn params(self) -> ParamsIter {
self.params
}
pub fn matched(&self) -> &str {
self.matched
}
}

View File

@ -1,382 +0,0 @@
mod horizontal;
mod nested;
mod vertical;
use crate::PathSegment;
use alloc::borrow::Cow;
pub use horizontal::*;
pub use nested::*;
pub use vertical::*;
#[derive(Debug)]
pub struct Routes<Children> {
base: Option<Cow<'static, str>>,
children: Children,
}
impl<Children> Routes<Children> {
pub fn new(children: Children) -> Self {
Self {
base: None,
children,
}
}
pub fn new_with_base(
children: Children,
base: impl Into<Cow<'static, str>>,
) -> Self {
Self {
base: Some(base.into()),
children,
}
}
}
impl<'a, Children> Routes<Children>
where
Children: MatchNestedRoutes<'a>,
{
pub fn match_route(&'a self, path: &'a str) -> Option<Children::Match> {
let path = match &self.base {
None => path,
Some(base) => {
let (base, path) = if base.starts_with('/') {
(base.trim_start_matches('/'), path.trim_start_matches('/'))
} else {
(base.as_ref(), path)
};
match path.strip_prefix(base) {
Some(path) => path,
None => return None,
}
}
};
let (matched, remaining) = self.children.match_nested(path);
let matched = matched?;
if !remaining.is_empty() {
None
} else {
Some(matched)
}
}
pub fn generate_routes(
&'a self,
) -> (
Option<&str>,
impl IntoIterator<Item = Vec<PathSegment>> + 'a,
) {
(self.base.as_deref(), self.children.generate_routes())
}
}
pub trait MatchInterface<'a> {
type Params: IntoIterator<Item = (&'a str, &'a str)>;
type Child;
type View;
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 Match: MatchInterface<'a>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str);
fn generate_routes(
&self,
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_;
}
#[cfg(test)]
mod tests {
use super::{NestedRoute, ParamSegment, Routes};
use crate::{
matching::{MatchInterface, StaticSegment, WildcardSegment},
PathSegment,
};
#[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());
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(paths, vec![vec![PathSegment::Static("/".into())]]);
}
#[test]
pub fn matches_nested_route() {
let routes = Routes::new(NestedRoute {
segments: StaticSegment(""),
children: NestedRoute {
segments: (StaticSegment("author"), StaticSegment("contact")),
children: (),
data: (),
view: "Contact Me",
},
data: (),
view: "Home",
});
// route generation
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(
paths,
vec![vec![
PathSegment::Static("".into()),
PathSegment::Static("author".into()),
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]
pub fn does_not_match_incomplete_route() {
let routes = Routes::new(NestedRoute {
segments: StaticSegment(""),
children: NestedRoute {
segments: (StaticSegment("author"), StaticSegment("contact")),
children: (),
data: (),
view: "Contact Me",
},
data: (),
view: "Home",
});
let matched = routes.match_route("/");
assert!(matched.is_none());
}
#[test]
pub fn chooses_between_nested_routes() {
let routes = Routes::new((
NestedRoute {
segments: StaticSegment("/"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: (StaticSegment("post"), ParamSegment("id")),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
));
// generates routes correctly
let (base, paths) = routes.generate_routes();
assert_eq!(base, None);
let paths = paths.into_iter().collect::<Vec<_>>();
assert_eq!(
paths,
vec![
vec![
PathSegment::Static("/".into()),
PathSegment::Static("".into()),
],
vec![
PathSegment::Static("/".into()),
PathSegment::Static("about".into())
],
vec![
PathSegment::Static("/blog".into()),
PathSegment::Static("".into()),
],
vec![
PathSegment::Static("/blog".into()),
PathSegment::Static("post".into()),
PathSegment::Param("id".into())
]
]
);
let matched = routes.match_route("/about").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/blog").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/blog/post/42").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("id", "42")]);
}
#[test]
pub fn arbitrary_nested_routes() {
let routes = Routes::new_with_base(
(
NestedRoute {
segments: StaticSegment("/"),
children: (
NestedRoute {
segments: StaticSegment("/"),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("about"),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("/blog"),
children: (
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: StaticSegment("category"),
children: (),
data: (),
view: || (),
},
NestedRoute {
segments: (
StaticSegment("post"),
ParamSegment("id"),
),
children: (),
data: (),
view: || (),
},
),
data: (),
view: || (),
},
NestedRoute {
segments: (
StaticSegment("/contact"),
WildcardSegment("any"),
),
children: (),
data: (),
view: || (),
},
),
"/portfolio",
);
// generates routes correctly
let (base, _paths) = routes.generate_routes();
assert_eq!(base, Some("/portfolio"));
let matched = routes.match_route("/about");
assert!(matched.is_none());
let matched = routes.match_route("/portfolio/about").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert!(params.is_empty());
let matched = routes.match_route("/portfolio/blog/post/42").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("id", "42")]);
let matched = routes.match_route("/portfolio/contact").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("any", "")]);
let matched = routes.match_route("/portfolio/contact/foobar").unwrap();
let params = matched.to_params().collect::<Vec<_>>();
assert_eq!(params, vec![("any", "foobar")]);
}
}
#[derive(Debug)]
pub struct PartialPathMatch<'a, ParamsIter> {
pub(crate) remaining: &'a str,
pub(crate) params: ParamsIter,
pub(crate) matched: &'a str,
}
impl<'a, ParamsIter> PartialPathMatch<'a, ParamsIter> {
pub fn new(
remaining: &'a str,
params: ParamsIter,
matched: &'a str,
) -> Self {
Self {
remaining,
params,
matched,
}
}
pub fn is_complete(&self) -> bool {
self.remaining.is_empty() || self.remaining == "/"
}
pub fn remaining(&self) -> &str {
self.remaining
}
pub fn params(self) -> ParamsIter {
self.params
}
pub fn matched(&self) -> &str {
self.matched
}
}

View File

@ -2,7 +2,8 @@ use super::{
MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
};
use crate::PathSegment;
use core::iter;
use alloc::vec::Vec;
use core::{fmt, iter};
mod tuples;
@ -14,7 +15,7 @@ pub struct NestedRoute<Segments, Children, Data, View> {
pub view: View,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(PartialEq, Eq)]
pub struct NestedMatch<'a, ParamsIter, Child, View> {
/// The portion of the full path matched only by this nested route.
matched: &'a str,
@ -25,6 +26,21 @@ pub struct NestedMatch<'a, ParamsIter, Child, View> {
view: &'a View,
}
impl<'a, ParamsIter, Child, View> fmt::Debug
for NestedMatch<'a, ParamsIter, Child, View>
where
ParamsIter: fmt::Debug,
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<'a, ParamsIter, Child, View> MatchInterface<'a>
for NestedMatch<'a, ParamsIter, Child, View>
where

View File

@ -1,7 +1,5 @@
use crate::{
matching::{MatchInterface, MatchNestedRoutes},
PathSegment,
};
use crate::{MatchInterface, MatchNestedRoutes, PathSegment};
use alloc::vec::Vec;
use core::iter;
use either_of::*;

View File

@ -1,4 +0,0 @@
extern crate alloc;
use alloc::{string::String, vec::Vec};
pub(crate) type Params<K> = Vec<(K, String)>;