mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 11:04:40 -05:00
@@ -4,7 +4,7 @@ mod routes;
|
||||
use leptos_meta::{provide_meta_context, Link, Meta, Stylesheet};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router, RoutingProgress},
|
||||
ParamSegment, StaticSegment,
|
||||
OptionalParamSegment, ParamSegment, StaticSegment,
|
||||
};
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use std::time::Duration;
|
||||
@@ -28,9 +28,7 @@ pub fn App() -> impl IntoView {
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
|
||||
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
|
||||
<Route path=ParamSegment("stories") view=Stories/>
|
||||
// TODO allow optional params without duplication
|
||||
<Route path=StaticSegment("") view=Stories/>
|
||||
<Route path=OptionalParamSegment("stories") view=Stories/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -4,7 +4,7 @@ mod routes;
|
||||
use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router, RoutingProgress},
|
||||
ParamSegment, StaticSegment,
|
||||
OptionalParamSegment, ParamSegment, StaticSegment,
|
||||
};
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use std::time::Duration;
|
||||
@@ -46,9 +46,7 @@ pub fn App() -> impl IntoView {
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
|
||||
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
|
||||
<Route path=ParamSegment("stories") view=Stories/>
|
||||
// TODO allow optional params without duplication
|
||||
<Route path=StaticSegment("") view=Stories/>
|
||||
<Route path=OptionalParamSegment("stories") view=Stories/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -4,7 +4,7 @@ mod routes;
|
||||
use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router},
|
||||
ParamSegment, StaticSegment,
|
||||
OptionalParamSegment, ParamSegment, StaticSegment,
|
||||
};
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -42,9 +42,7 @@ pub fn App() -> impl IntoView {
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
|
||||
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
|
||||
<Route path=ParamSegment("stories") view=Stories/>
|
||||
// TODO allow optional params without duplication
|
||||
<Route path=StaticSegment("") view=Stories/>
|
||||
<Route path=OptionalParamSegment("stories") view=Stories/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -4,7 +4,7 @@ mod routes;
|
||||
use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router, RoutingProgress},
|
||||
ParamSegment, StaticSegment,
|
||||
OptionalParamSegment, ParamSegment, StaticSegment,
|
||||
};
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use std::time::Duration;
|
||||
@@ -46,9 +46,7 @@ pub fn App() -> impl IntoView {
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
|
||||
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
|
||||
<Route path=ParamSegment("stories") view=Stories/>
|
||||
// TODO allow optional params without duplication
|
||||
<Route path=StaticSegment("") view=Stories/>
|
||||
<Route path=OptionalParamSegment("stories") view=Stories/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -35,7 +35,7 @@ use leptos_router::{
|
||||
components::provide_server_redirect,
|
||||
location::RequestUrl,
|
||||
static_routes::{RegenerationFn, ResolvedStaticPath},
|
||||
Method, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
ExpandOptionals, Method, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
@@ -901,7 +901,7 @@ trait ActixPath {
|
||||
fn to_actix_path(&self) -> String;
|
||||
}
|
||||
|
||||
impl ActixPath for &[PathSegment] {
|
||||
impl ActixPath for Vec<PathSegment> {
|
||||
fn to_actix_path(&self) -> String {
|
||||
let mut path = String::new();
|
||||
for segment in self.iter() {
|
||||
@@ -923,6 +923,14 @@ impl ActixPath for &[PathSegment] {
|
||||
path.push_str(":.*}");
|
||||
}
|
||||
PathSegment::Unit => {}
|
||||
PathSegment::OptionalParam(_) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!(
|
||||
"to_axum_path should only be called on expanded \
|
||||
paths, which do not have OptionalParam any longer"
|
||||
);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
path
|
||||
@@ -938,23 +946,34 @@ pub struct ActixRouteListing {
|
||||
regenerate: Vec<RegenerationFn>,
|
||||
}
|
||||
|
||||
impl From<RouteListing> for ActixRouteListing {
|
||||
fn from(value: RouteListing) -> Self {
|
||||
let path = value.path().to_actix_path();
|
||||
let path = if path.is_empty() {
|
||||
"/".to_string()
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let mode = value.mode();
|
||||
let methods = value.methods().collect();
|
||||
let regenerate = value.regenerate().into();
|
||||
Self {
|
||||
path,
|
||||
mode: mode.clone(),
|
||||
methods,
|
||||
regenerate,
|
||||
}
|
||||
trait IntoRouteListing: Sized {
|
||||
fn into_route_listing(self) -> Vec<ActixRouteListing>;
|
||||
}
|
||||
|
||||
impl IntoRouteListing for RouteListing {
|
||||
fn into_route_listing(self) -> Vec<ActixRouteListing> {
|
||||
self.path()
|
||||
.to_vec()
|
||||
.expand_optionals()
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let path = path.to_actix_path();
|
||||
let path = if path.is_empty() {
|
||||
"/".to_string()
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let mode = self.mode();
|
||||
let methods = self.methods().collect();
|
||||
let regenerate = self.regenerate().into();
|
||||
ActixRouteListing {
|
||||
path,
|
||||
mode: mode.clone(),
|
||||
methods,
|
||||
regenerate,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,7 +1052,7 @@ where
|
||||
let mut routes = routes
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.map(ActixRouteListing::from)
|
||||
.flat_map(IntoRouteListing::into_route_listing)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
|
||||
@@ -66,7 +66,7 @@ use leptos_router::{
|
||||
components::provide_server_redirect,
|
||||
location::RequestUrl,
|
||||
static_routes::{RegenerationFn, StaticParamsMap},
|
||||
PathSegment, RouteList, RouteListing, SsrMode,
|
||||
ExpandOptionals, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
};
|
||||
#[cfg(feature = "default")]
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -1267,23 +1267,34 @@ pub struct AxumRouteListing {
|
||||
regenerate: Vec<RegenerationFn>,
|
||||
}
|
||||
|
||||
impl From<RouteListing> for AxumRouteListing {
|
||||
fn from(value: RouteListing) -> Self {
|
||||
let path = value.path().to_axum_path();
|
||||
let path = if path.is_empty() {
|
||||
"/".to_string()
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let mode = value.mode();
|
||||
let methods = value.methods().collect();
|
||||
let regenerate = value.regenerate().into();
|
||||
Self {
|
||||
path,
|
||||
mode: mode.clone(),
|
||||
methods,
|
||||
regenerate,
|
||||
}
|
||||
trait IntoRouteListing: Sized {
|
||||
fn into_route_listing(self) -> Vec<AxumRouteListing>;
|
||||
}
|
||||
|
||||
impl IntoRouteListing for RouteListing {
|
||||
fn into_route_listing(self) -> Vec<AxumRouteListing> {
|
||||
self.path()
|
||||
.to_vec()
|
||||
.expand_optionals()
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let path = path.to_axum_path();
|
||||
let path = if path.is_empty() {
|
||||
"/".to_string()
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let mode = self.mode();
|
||||
let methods = self.methods().collect();
|
||||
let regenerate = self.regenerate().into();
|
||||
AxumRouteListing {
|
||||
path,
|
||||
mode: mode.clone(),
|
||||
methods,
|
||||
regenerate,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1367,7 +1378,7 @@ where
|
||||
let mut routes = routes
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.map(AxumRouteListing::from)
|
||||
.flat_map(IntoRouteListing::into_route_listing)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
@@ -1700,7 +1711,7 @@ trait AxumPath {
|
||||
fn to_axum_path(&self) -> String;
|
||||
}
|
||||
|
||||
impl AxumPath for &[PathSegment] {
|
||||
impl AxumPath for Vec<PathSegment> {
|
||||
fn to_axum_path(&self) -> String {
|
||||
let mut path = String::new();
|
||||
for segment in self.iter() {
|
||||
@@ -1720,6 +1731,14 @@ impl AxumPath for &[PathSegment] {
|
||||
path.push_str(s);
|
||||
}
|
||||
PathSegment::Unit => {}
|
||||
PathSegment::OptionalParam(_) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!(
|
||||
"to_axum_path should only be called on expanded \
|
||||
paths, which do not have OptionalParam any longer"
|
||||
);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
path
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
hooks::Matched,
|
||||
location::{LocationProvider, Url},
|
||||
matching::Routes,
|
||||
matching::{MatchParams, Routes},
|
||||
params::ParamsMap,
|
||||
view_transition::start_view_transition,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, PathSegment,
|
||||
RouteList, RouteListing, RouteMatchId,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, PathSegment, RouteList,
|
||||
RouteListing, RouteMatchId,
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use either_of::Either;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::{PartialPathMatch, PathSegment};
|
||||
use std::borrow::Cow;
|
||||
mod param_segments;
|
||||
mod static_segment;
|
||||
mod tuples;
|
||||
@@ -13,12 +12,9 @@ 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: IntoIterator<Item = (Cow<'static, str>, String)>;
|
||||
const OPTIONAL: bool = false;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>>;
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>);
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@ use std::borrow::Cow;
|
||||
///
|
||||
/// // Manual definition
|
||||
/// let manual = (ParamSegment("message"),);
|
||||
/// let (key, value) = manual.test(path)?.params().last()?;
|
||||
/// let params = manual.test(path)?.params();
|
||||
/// let (key, value) = params.last()?;
|
||||
///
|
||||
/// assert_eq!(key, "message");
|
||||
/// assert_eq!(value, "hello");
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/:message");
|
||||
/// let (key, value) = using_macro.test(path)?.params().last()?;
|
||||
/// let params = using_macro.test(path)?.params();
|
||||
/// let (key, value) = params.last()?;
|
||||
///
|
||||
/// assert_eq!(key, "message");
|
||||
/// assert_eq!(value, "hello");
|
||||
@@ -33,12 +35,7 @@ use std::borrow::Cow;
|
||||
pub struct ParamSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for ParamSegment {
|
||||
type ParamsIter = iter::Once<(Cow<'static, str>, String)>;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut param_offset = 0;
|
||||
let mut param_len = 0;
|
||||
@@ -66,10 +63,10 @@ impl PossibleRouteMatch for ParamSegment {
|
||||
}
|
||||
|
||||
let (matched, remaining) = path.split_at(matched_len);
|
||||
let param_value = iter::once((
|
||||
let param_value = vec![(
|
||||
Cow::Borrowed(self.0),
|
||||
path[param_offset..param_len + param_offset].to_string(),
|
||||
));
|
||||
)];
|
||||
Some(PartialPathMatch::new(remaining, param_value, matched))
|
||||
}
|
||||
|
||||
@@ -93,14 +90,16 @@ impl PossibleRouteMatch for ParamSegment {
|
||||
///
|
||||
/// // Manual definition
|
||||
/// let manual = (StaticSegment("echo"), WildcardSegment("kitchen_sink"));
|
||||
/// let (key, value) = manual.test(path)?.params().last()?;
|
||||
/// let params = manual.test(path)?.params();
|
||||
/// let (key, value) = params.last()?;
|
||||
///
|
||||
/// assert_eq!(key, "kitchen_sink");
|
||||
/// assert_eq!(value, "send/sync/and/static");
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/echo/*else");
|
||||
/// let (key, value) = using_macro.test(path)?.params().last()?;
|
||||
/// let params = using_macro.test(path)?.params();
|
||||
/// let (key, value) = params.last()?;
|
||||
///
|
||||
/// assert_eq!(key, "else");
|
||||
/// assert_eq!(value, "send/sync/and/static");
|
||||
@@ -122,12 +121,7 @@ impl PossibleRouteMatch for ParamSegment {
|
||||
pub struct WildcardSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for WildcardSegment {
|
||||
type ParamsIter = iter::Once<(Cow<'static, str>, String)>;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut param_offset = 0;
|
||||
let mut param_len = 0;
|
||||
@@ -148,7 +142,11 @@ impl PossibleRouteMatch for WildcardSegment {
|
||||
Cow::Borrowed(self.0),
|
||||
path[param_offset..param_len + param_offset].to_string(),
|
||||
));
|
||||
Some(PartialPathMatch::new(remaining, param_value, matched))
|
||||
Some(PartialPathMatch::new(
|
||||
remaining,
|
||||
param_value.into_iter().collect(),
|
||||
matched,
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
@@ -156,10 +154,64 @@ impl PossibleRouteMatch for WildcardSegment {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct OptionalParamSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for OptionalParamSegment {
|
||||
const OPTIONAL: bool = true;
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut param_offset = 0;
|
||||
let mut param_len = 0;
|
||||
let mut test = path.chars();
|
||||
|
||||
// match an initial /
|
||||
if let Some('/') = test.next() {
|
||||
matched_len += 1;
|
||||
param_offset = 1;
|
||||
}
|
||||
for char in test {
|
||||
// when we get a closing /, stop matching
|
||||
if char == '/' {
|
||||
break;
|
||||
}
|
||||
// otherwise, push into the matched param
|
||||
else {
|
||||
matched_len += char.len_utf8();
|
||||
param_len += char.len_utf8();
|
||||
}
|
||||
}
|
||||
|
||||
let matched_len = if matched_len == 1 && path.starts_with('/') {
|
||||
0
|
||||
} else {
|
||||
matched_len
|
||||
};
|
||||
let (matched, remaining) = path.split_at(matched_len);
|
||||
let param_value = (matched_len > 0)
|
||||
.then(|| {
|
||||
(
|
||||
Cow::Borrowed(self.0),
|
||||
path[param_offset..param_len + param_offset].to_string(),
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
Some(PartialPathMatch::new(remaining, param_value, matched))
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
path.push(PathSegment::OptionalParam(self.0.into()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PossibleRouteMatch;
|
||||
use crate::{ParamSegment, StaticSegment, WildcardSegment};
|
||||
use crate::{
|
||||
OptionalParamSegment, ParamSegment, StaticSegment, WildcardSegment,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn single_param_match() {
|
||||
@@ -168,7 +220,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
}
|
||||
|
||||
@@ -179,7 +231,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
}
|
||||
|
||||
@@ -190,7 +242,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
assert_eq!(params[1], ("b".into(), "bar".into()));
|
||||
}
|
||||
@@ -206,7 +258,94 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar/////");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("rest".into(), "////".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_param_can_match() {
|
||||
let path = "/foo";
|
||||
let def = OptionalParamSegment("a");
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_param_can_not_match() {
|
||||
let path = "/";
|
||||
let def = OptionalParamSegment("a");
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
let params = matched.params();
|
||||
assert_eq!(params.first(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_params_match_first() {
|
||||
let path = "/foo";
|
||||
let def = (OptionalParamSegment("a"), OptionalParamSegment("b"));
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_params_can_match_both() {
|
||||
let path = "/foo/bar";
|
||||
let def = (OptionalParamSegment("a"), OptionalParamSegment("b"));
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
assert_eq!(params[1], ("b".into(), "bar".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching_after_optional_param() {
|
||||
let path = "/bar";
|
||||
let def = (OptionalParamSegment("a"), StaticSegment("bar"));
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_optional_params_match_first() {
|
||||
let path = "/foo/bar";
|
||||
let def = (
|
||||
OptionalParamSegment("a"),
|
||||
OptionalParamSegment("b"),
|
||||
StaticSegment("bar"),
|
||||
);
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_optionals_can_match_both() {
|
||||
let path = "/foo/qux/bar";
|
||||
let def = (
|
||||
OptionalParamSegment("a"),
|
||||
OptionalParamSegment("b"),
|
||||
StaticSegment("bar"),
|
||||
);
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/qux/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("a".into(), "foo".into()));
|
||||
assert_eq!(params[1], ("b".into(), "qux".into()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
|
||||
use core::iter;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl PossibleRouteMatch for () {
|
||||
type ParamsIter = iter::Empty<(Cow<'static, str>, String)>;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
Some(PartialPathMatch::new(path, iter::empty(), ""))
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
Some(PartialPathMatch::new(path, vec![], ""))
|
||||
}
|
||||
|
||||
fn generate_path(&self, _path: &mut Vec<PathSegment>) {}
|
||||
@@ -44,14 +38,14 @@ impl AsPath for &'static str {
|
||||
///
|
||||
/// // Params are empty as we had no `ParamSegement`s or `WildcardSegment`s
|
||||
/// // If you did have additional dynamic segments, this would not be empty.
|
||||
/// assert_eq!(matched.params().count(), 0);
|
||||
/// assert_eq!(matched.params().len(), 0);
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/users");
|
||||
/// let matched = manual.test(path)?;
|
||||
/// assert_eq!(matched.matched(), "/users");
|
||||
///
|
||||
/// assert_eq!(matched.params().count(), 0);
|
||||
/// assert_eq!(matched.params().len(), 0);
|
||||
///
|
||||
/// # Some(())
|
||||
/// # })().unwrap();
|
||||
@@ -60,12 +54,7 @@ impl AsPath for &'static str {
|
||||
pub struct StaticSegment<T: AsPath>(pub T);
|
||||
|
||||
impl<T: AsPath> PossibleRouteMatch for StaticSegment<T> {
|
||||
type ParamsIter = iter::Empty<(Cow<'static, str>, String)>;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let mut matched_len = 0;
|
||||
let mut test = path.chars().peekable();
|
||||
let mut this = self.0.as_path().chars();
|
||||
@@ -113,8 +102,7 @@ impl<T: AsPath> PossibleRouteMatch for StaticSegment<T> {
|
||||
// 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, iter::empty(), matched))
|
||||
has_matched.then(|| PartialPathMatch::new(remaining, vec![], matched))
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
@@ -151,7 +139,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -162,7 +150,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -187,7 +175,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -198,7 +186,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -209,7 +197,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -220,7 +208,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -252,7 +240,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
@@ -270,7 +258,7 @@ mod tests {
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params().collect::<Vec<_>>();
|
||||
let params = matched.params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,4 @@
|
||||
use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
|
||||
use core::iter::Chain;
|
||||
|
||||
macro_rules! chain_types {
|
||||
($first:ty, $second:ty, ) => {
|
||||
Chain<
|
||||
$first,
|
||||
<<$second as PossibleRouteMatch>::ParamsIter as IntoIterator>::IntoIter
|
||||
>
|
||||
};
|
||||
($first:ty, $second:ty, $($rest:ty,)+) => {
|
||||
chain_types!(
|
||||
Chain<
|
||||
$first,
|
||||
<<$second as PossibleRouteMatch>::ParamsIter as IntoIterator>::IntoIter,
|
||||
>,
|
||||
$($rest,)+
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($first:ident => $($ty:ident),*) => {
|
||||
@@ -27,34 +8,69 @@ macro_rules! tuples {
|
||||
$first: PossibleRouteMatch,
|
||||
$($ty: PossibleRouteMatch),*,
|
||||
{
|
||||
type ParamsIter = chain_types!(<<$first>::ParamsIter as IntoIterator>::IntoIter, $($ty,)*);
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
// on the first run, include all optionals
|
||||
let mut include_optionals = {
|
||||
[$first::OPTIONAL, $($ty::OPTIONAL),*].into_iter().filter(|n| *n).count()
|
||||
};
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
let mut matched_len = 0;
|
||||
#[allow(non_snake_case)]
|
||||
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,
|
||||
matched,
|
||||
params
|
||||
} = $ty.test(remaining)?;
|
||||
matched_len += matched.len();
|
||||
let params_iter = params_iter.chain(params);
|
||||
)*
|
||||
Some(PartialPathMatch {
|
||||
remaining,
|
||||
matched: &path[0..matched_len],
|
||||
params: params_iter
|
||||
})
|
||||
|
||||
loop {
|
||||
let mut nth_field = 0;
|
||||
let mut matched_len = 0;
|
||||
let mut r = path;
|
||||
|
||||
let mut p = Vec::new();
|
||||
let mut m = String::new();
|
||||
|
||||
if !$first::OPTIONAL || nth_field < include_optionals {
|
||||
match $first.test(r) {
|
||||
None => {
|
||||
return None;
|
||||
},
|
||||
Some(PartialPathMatch { remaining, matched, params }) => {
|
||||
p.extend(params.into_iter());
|
||||
m.push_str(matched);
|
||||
r = remaining;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
matched_len += m.len();
|
||||
$(
|
||||
if $ty::OPTIONAL {
|
||||
nth_field += 1;
|
||||
}
|
||||
if !$ty::OPTIONAL || nth_field < include_optionals {
|
||||
let PartialPathMatch {
|
||||
remaining,
|
||||
matched,
|
||||
params
|
||||
} = match $ty.test(r) {
|
||||
None => if $ty::OPTIONAL {
|
||||
return None;
|
||||
} else {
|
||||
if include_optionals == 0 {
|
||||
return None;
|
||||
}
|
||||
include_optionals -= 1;
|
||||
continue;
|
||||
},
|
||||
Some(v) => v,
|
||||
};
|
||||
r = remaining;
|
||||
matched_len += matched.len();
|
||||
p.extend(params);
|
||||
}
|
||||
)*
|
||||
return Some(PartialPathMatch {
|
||||
remaining: r,
|
||||
matched: &path[0..matched_len],
|
||||
params: p
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
@@ -74,12 +90,7 @@ where
|
||||
Self: core::fmt::Debug,
|
||||
A: PossibleRouteMatch,
|
||||
{
|
||||
type ParamsIter = A::ParamsIter;
|
||||
|
||||
fn test<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<PartialPathMatch<'a, Self::ParamsIter>> {
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
let remaining = path;
|
||||
let PartialPathMatch {
|
||||
remaining,
|
||||
|
||||
@@ -103,9 +103,7 @@ pub trait MatchInterface {
|
||||
}
|
||||
|
||||
pub trait MatchParams {
|
||||
type Params: IntoIterator<Item = (Cow<'static, str>, String)>;
|
||||
|
||||
fn to_params(&self) -> Self::Params;
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)>;
|
||||
}
|
||||
|
||||
pub trait MatchNestedRoutes {
|
||||
@@ -255,13 +253,13 @@ mod tests {
|
||||
);
|
||||
|
||||
let matched = routes.match_route("/about").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert!(params.is_empty());
|
||||
let matched = routes.match_route("/blog").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert!(params.is_empty());
|
||||
let matched = routes.match_route("/blog/post/42").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert_eq!(params, vec![("id".into(), "42".into())]);
|
||||
}
|
||||
|
||||
@@ -297,34 +295,34 @@ mod tests {
|
||||
assert!(matched.is_none());
|
||||
|
||||
let matched = routes.match_route("/portfolio/about").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert!(params.is_empty());
|
||||
|
||||
let matched = routes.match_route("/portfolio/blog/post/42").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert_eq!(params, vec![("id".into(), "42".into())]);
|
||||
|
||||
let matched = routes.match_route("/portfolio/contact").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert_eq!(params, vec![("any".into(), "".into())]);
|
||||
|
||||
let matched = routes.match_route("/portfolio/contact/foobar").unwrap();
|
||||
let params = matched.to_params().collect::<Vec<_>>();
|
||||
let params = matched.to_params();
|
||||
assert_eq!(params, vec![("any".into(), "foobar".into())]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PartialPathMatch<'a, ParamsIter> {
|
||||
pub struct PartialPathMatch<'a> {
|
||||
pub(crate) remaining: &'a str,
|
||||
pub(crate) params: ParamsIter,
|
||||
pub(crate) params: Vec<(Cow<'static, str>, String)>,
|
||||
pub(crate) matched: &'a str,
|
||||
}
|
||||
|
||||
impl<'a, ParamsIter> PartialPathMatch<'a, ParamsIter> {
|
||||
impl<'a> PartialPathMatch<'a> {
|
||||
pub fn new(
|
||||
remaining: &'a str,
|
||||
params: ParamsIter,
|
||||
params: Vec<(Cow<'static, str>, String)>,
|
||||
matched: &'a str,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -342,7 +340,7 @@ impl<'a, ParamsIter> PartialPathMatch<'a, ParamsIter> {
|
||||
self.remaining
|
||||
}
|
||||
|
||||
pub fn params(self) -> ParamsIter {
|
||||
pub fn params(self) -> Vec<(Cow<'static, str>, String)> {
|
||||
self.params
|
||||
}
|
||||
|
||||
|
||||
@@ -96,21 +96,19 @@ impl<Segments, Data, View> NestedRoute<Segments, (), Data, View> {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct NestedMatch<ParamsIter, Child, View> {
|
||||
pub struct NestedMatch<Child, View> {
|
||||
id: RouteMatchId,
|
||||
/// The portion of the full path matched only by this nested route.
|
||||
matched: String,
|
||||
/// The map of params matched only by this nested route.
|
||||
params: ParamsIter,
|
||||
params: Vec<(Cow<'static, str>, String)>,
|
||||
/// The nested route.
|
||||
child: Option<Child>,
|
||||
view_fn: View,
|
||||
}
|
||||
|
||||
impl<ParamsIter, Child, View> fmt::Debug
|
||||
for NestedMatch<ParamsIter, Child, View>
|
||||
impl<Child, View> fmt::Debug for NestedMatch<Child, View>
|
||||
where
|
||||
ParamsIter: fmt::Debug,
|
||||
Child: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@@ -122,21 +120,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<ParamsIter, Child, View> MatchParams
|
||||
for NestedMatch<ParamsIter, Child, View>
|
||||
where
|
||||
ParamsIter: IntoIterator<Item = (Cow<'static, str>, String)> + Clone,
|
||||
{
|
||||
type Params = ParamsIter;
|
||||
|
||||
impl<Child, View> MatchParams for NestedMatch<Child, View> {
|
||||
#[inline(always)]
|
||||
fn to_params(&self) -> Self::Params {
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
|
||||
self.params.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<ParamsIter, Child, View> MatchInterface
|
||||
for NestedMatch<ParamsIter, Child, View>
|
||||
impl<Child, View> MatchInterface for NestedMatch<Child, View>
|
||||
where
|
||||
Child: MatchInterface + MatchParams + 'static,
|
||||
View: ChooseView,
|
||||
@@ -161,21 +152,13 @@ impl<Segments, Children, Data, View> MatchNestedRoutes
|
||||
where
|
||||
Self: 'static,
|
||||
Segments: PossibleRouteMatch + std::fmt::Debug,
|
||||
<<Segments as PossibleRouteMatch>::ParamsIter as IntoIterator>::IntoIter: Clone,
|
||||
Children: MatchNestedRoutes,
|
||||
<<<Children as MatchNestedRoutes>::Match as MatchParams>::Params as IntoIterator>::IntoIter: Clone,
|
||||
Children::Match: MatchParams,
|
||||
Children: 'static,
|
||||
<Children::Match as MatchParams>::Params: Clone,
|
||||
Children::Match: MatchParams,
|
||||
Children: 'static,
|
||||
View: ChooseView + Clone,
|
||||
{
|
||||
type Data = Data;
|
||||
type Match = NestedMatch<iter::Chain<
|
||||
<Segments::ParamsIter as IntoIterator>::IntoIter,
|
||||
Either<iter::Empty::<
|
||||
(Cow<'static, str>, String)
|
||||
>, <<Children::Match as MatchParams>::Params as IntoIterator>::IntoIter>
|
||||
>, Children::Match, View>;
|
||||
type Match = NestedMatch<Children::Match, View>;
|
||||
|
||||
fn match_nested<'a>(
|
||||
&'a self,
|
||||
@@ -186,33 +169,34 @@ where
|
||||
.and_then(
|
||||
|PartialPathMatch {
|
||||
remaining,
|
||||
params,
|
||||
mut params,
|
||||
matched,
|
||||
}| {
|
||||
let (_, inner, remaining) = match &self.children {
|
||||
None => (None, None, remaining),
|
||||
Some(children) => {
|
||||
let (inner, remaining) = children.match_nested(remaining);
|
||||
let (inner, remaining) =
|
||||
children.match_nested(remaining);
|
||||
let (id, inner) = inner?;
|
||||
(Some(id), Some(inner), remaining)
|
||||
(Some(id), Some(inner), remaining)
|
||||
}
|
||||
};
|
||||
let params = params.into_iter();
|
||||
let inner_params = match &inner {
|
||||
None => Either::Left(iter::empty()),
|
||||
Some(inner) => Either::Right(inner.to_params().into_iter())
|
||||
};
|
||||
let inner_params = inner
|
||||
.as_ref()
|
||||
.map(|inner| inner.to_params())
|
||||
.unwrap_or_default();
|
||||
|
||||
let id = RouteMatchId(self.id);
|
||||
|
||||
if remaining.is_empty() || remaining == "/" {
|
||||
params.extend(inner_params);
|
||||
Some((
|
||||
Some((
|
||||
id,
|
||||
NestedMatch {
|
||||
id,
|
||||
matched: matched.to_string(),
|
||||
params: params.chain(inner_params),
|
||||
params,
|
||||
child: inner,
|
||||
view_fn: self.view.clone(),
|
||||
},
|
||||
@@ -238,9 +222,9 @@ where
|
||||
let regenerate = match &ssr_mode {
|
||||
SsrMode::Static(data) => match data.regenerate.as_ref() {
|
||||
None => vec![],
|
||||
Some(regenerate) => vec![regenerate.clone()]
|
||||
}
|
||||
_ => vec![]
|
||||
Some(regenerate) => vec![regenerate.clone()],
|
||||
},
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
match children {
|
||||
@@ -248,32 +232,41 @@ where
|
||||
segments: segment_routes,
|
||||
ssr_mode,
|
||||
methods,
|
||||
regenerate
|
||||
regenerate,
|
||||
})),
|
||||
Some(children) => {
|
||||
Either::Right(children.generate_routes().into_iter().map(move |child| {
|
||||
// extend this route's segments with child segments
|
||||
let segments = segment_routes.clone().into_iter().chain(child.segments).collect();
|
||||
Either::Right(children.generate_routes().into_iter().map(
|
||||
move |child| {
|
||||
// extend this route's segments with child segments
|
||||
let segments = segment_routes
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(child.segments)
|
||||
.collect();
|
||||
|
||||
let mut methods = methods.clone();
|
||||
methods.extend(child.methods);
|
||||
let mut methods = methods.clone();
|
||||
methods.extend(child.methods);
|
||||
|
||||
let mut regenerate = regenerate.clone();
|
||||
regenerate.extend(child.regenerate);
|
||||
let mut regenerate = regenerate.clone();
|
||||
regenerate.extend(child.regenerate);
|
||||
|
||||
if child.ssr_mode > ssr_mode {
|
||||
GeneratedRouteData {
|
||||
segments,
|
||||
ssr_mode: child.ssr_mode,
|
||||
methods, 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,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GeneratedRouteData {
|
||||
segments,
|
||||
ssr_mode: ssr_mode.clone(), methods, regenerate
|
||||
}
|
||||
}
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ use either_of::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl MatchParams for () {
|
||||
type Params = iter::Empty<(Cow<'static, str>, String)>;
|
||||
|
||||
fn to_params(&self) -> Self::Params {
|
||||
iter::empty()
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +51,7 @@ impl<A> MatchParams for (A,)
|
||||
where
|
||||
A: MatchParams,
|
||||
{
|
||||
type Params = A::Params;
|
||||
|
||||
fn to_params(&self) -> Self::Params {
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
|
||||
self.0.to_params()
|
||||
}
|
||||
}
|
||||
@@ -105,15 +101,10 @@ where
|
||||
A: MatchParams,
|
||||
B: MatchParams,
|
||||
{
|
||||
type Params = Either<
|
||||
<A::Params as IntoIterator>::IntoIter,
|
||||
<B::Params as IntoIterator>::IntoIter,
|
||||
>;
|
||||
|
||||
fn to_params(&self) -> Self::Params {
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
|
||||
match self {
|
||||
Either::Left(i) => Either::Left(i.to_params().into_iter()),
|
||||
Either::Right(i) => Either::Right(i.to_params().into_iter()),
|
||||
Either::Left(i) => i.to_params(),
|
||||
Either::Right(i) => i.to_params(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,13 +199,9 @@ macro_rules! tuples {
|
||||
where
|
||||
$($ty: MatchParams),*,
|
||||
{
|
||||
type Params = $either<$(
|
||||
<$ty::Params as IntoIterator>::IntoIter,
|
||||
)*>;
|
||||
|
||||
fn to_params(&self) -> Self::Params {
|
||||
fn to_params(&self) -> Vec<(Cow<'static, str>, String)> {
|
||||
match self {
|
||||
$($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)*
|
||||
$($either::$ty(i) => i.to_params(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub enum PathSegment {
|
||||
Unit,
|
||||
Static(Cow<'static, str>),
|
||||
Param(Cow<'static, str>),
|
||||
OptionalParam(Cow<'static, str>),
|
||||
Splat(Cow<'static, str>),
|
||||
}
|
||||
|
||||
@@ -14,7 +15,98 @@ impl PathSegment {
|
||||
PathSegment::Unit => "",
|
||||
PathSegment::Static(i) => i,
|
||||
PathSegment::Param(i) => i,
|
||||
PathSegment::OptionalParam(i) => i,
|
||||
PathSegment::Splat(i) => i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExpandOptionals {
|
||||
fn expand_optionals(&self) -> Vec<Vec<PathSegment>>;
|
||||
}
|
||||
|
||||
impl ExpandOptionals for Vec<PathSegment> {
|
||||
fn expand_optionals(&self) -> Vec<Vec<PathSegment>> {
|
||||
let mut segments = vec![self.to_vec()];
|
||||
let mut checked = Vec::new();
|
||||
while let Some(next_to_check) = segments.pop() {
|
||||
let mut had_optional = false;
|
||||
for (idx, segment) in next_to_check.iter().enumerate() {
|
||||
if let PathSegment::OptionalParam(name) = segment {
|
||||
had_optional = true;
|
||||
let mut unit_variant = next_to_check.to_vec();
|
||||
unit_variant.remove(idx);
|
||||
let mut param_variant = next_to_check.to_vec();
|
||||
param_variant[idx] = PathSegment::Param(name.clone());
|
||||
segments.push(unit_variant);
|
||||
segments.push(param_variant);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !had_optional {
|
||||
checked.push(next_to_check.to_vec());
|
||||
}
|
||||
}
|
||||
checked
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ExpandOptionals, PathSegment};
|
||||
|
||||
#[test]
|
||||
fn expand_optionals_on_plain() {
|
||||
let plain = vec![
|
||||
PathSegment::Static("a".into()),
|
||||
PathSegment::Param("b".into()),
|
||||
];
|
||||
assert_eq!(plain.expand_optionals(), vec![plain]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_optionals_once() {
|
||||
let plain = vec![
|
||||
PathSegment::OptionalParam("a".into()),
|
||||
PathSegment::Static("b".into()),
|
||||
];
|
||||
assert_eq!(
|
||||
plain.expand_optionals(),
|
||||
vec![
|
||||
vec![
|
||||
PathSegment::Param("a".into()),
|
||||
PathSegment::Static("b".into())
|
||||
],
|
||||
vec![PathSegment::Static("b".into())]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_optionals_twice() {
|
||||
let plain = vec![
|
||||
PathSegment::OptionalParam("a".into()),
|
||||
PathSegment::OptionalParam("b".into()),
|
||||
PathSegment::Static("c".into()),
|
||||
];
|
||||
assert_eq!(
|
||||
plain.expand_optionals(),
|
||||
vec![
|
||||
vec![
|
||||
PathSegment::Param("a".into()),
|
||||
PathSegment::Param("b".into()),
|
||||
PathSegment::Static("c".into()),
|
||||
],
|
||||
vec![
|
||||
PathSegment::Param("a".into()),
|
||||
PathSegment::Static("c".into()),
|
||||
],
|
||||
vec![
|
||||
PathSegment::Param("b".into()),
|
||||
PathSegment::Static("c".into()),
|
||||
],
|
||||
vec![PathSegment::Static("c".into())]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
use super::PartialPathMatch;
|
||||
|
||||
pub trait ChooseRoute {
|
||||
fn choose_route<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
) -> Option<
|
||||
PartialPathMatch<'a, impl IntoIterator<Item = (&'a str, &'a str)>>,
|
||||
>;
|
||||
fn choose_route<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
|
||||
}
|
||||
|
||||
@@ -247,6 +247,7 @@ impl StaticPath {
|
||||
}
|
||||
paths = new_paths;
|
||||
}
|
||||
OptionalParam(_) => todo!(),
|
||||
}
|
||||
}
|
||||
paths
|
||||
|
||||
@@ -18,4 +18,4 @@ proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos_router = { version = "0.7.0-beta" }
|
||||
leptos_router = { path = "../router" }
|
||||
|
||||
@@ -14,12 +14,16 @@ const RFC3986_PCHAR_OTHER: [char; 1] = ['@'];
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use leptos_router::{path, ParamSegment, StaticSegment, WildcardSegment};
|
||||
/// use leptos_router::{
|
||||
/// path, OptionalParamSegment, ParamSegment, StaticSegment,
|
||||
/// WildcardSegment,
|
||||
/// };
|
||||
///
|
||||
/// let path = path!("/foo/:bar/*any");
|
||||
/// let path = path!("/foo/:bar/:baz?/*any");
|
||||
/// let output = (
|
||||
/// StaticSegment("foo"),
|
||||
/// ParamSegment("bar"),
|
||||
/// OptionalParamSegment("baz"),
|
||||
/// WildcardSegment("any"),
|
||||
/// );
|
||||
///
|
||||
@@ -41,6 +45,7 @@ struct Segments(pub Vec<Segment>);
|
||||
enum Segment {
|
||||
Static(String),
|
||||
Param(String),
|
||||
OptionalParam(String),
|
||||
Wildcard(String),
|
||||
}
|
||||
|
||||
@@ -93,7 +98,11 @@ impl SegmentParser {
|
||||
|
||||
for segment in current_str.split('/') {
|
||||
if let Some(segment) = segment.strip_prefix(':') {
|
||||
segments.push(Segment::Param(segment.to_string()));
|
||||
if let Some(segment) = segment.strip_suffix('?') {
|
||||
segments.push(Segment::OptionalParam(segment.to_string()));
|
||||
} else {
|
||||
segments.push(Segment::Param(segment.to_string()));
|
||||
}
|
||||
} else if let Some(segment) = segment.strip_prefix('*') {
|
||||
segments.push(Segment::Wildcard(segment.to_string()));
|
||||
} else {
|
||||
@@ -156,6 +165,10 @@ impl ToTokens for Segment {
|
||||
Segment::Param(p) => {
|
||||
tokens.extend(quote! { leptos_router::ParamSegment(#p) });
|
||||
}
|
||||
Segment::OptionalParam(p) => {
|
||||
tokens
|
||||
.extend(quote! { leptos_router::OptionalParamSegment(#p) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use leptos_router::{ParamSegment, StaticSegment, WildcardSegment};
|
||||
use leptos_router::{
|
||||
OptionalParamSegment, ParamSegment, StaticSegment, WildcardSegment,
|
||||
};
|
||||
use leptos_router_macro::path;
|
||||
|
||||
#[test]
|
||||
@@ -86,6 +88,12 @@ fn parses_single_param() {
|
||||
assert_eq!(output, (ParamSegment("id"),));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_optional_param() {
|
||||
let output = path!("/:id?");
|
||||
assert_eq!(output, (OptionalParamSegment("id"),));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_static_and_param() {
|
||||
let output = path!("/home/:id");
|
||||
@@ -144,9 +152,22 @@ fn parses_consecutive_param() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_consecutive_optional_param() {
|
||||
let output = path!("/:foo?/:bar?/:baz?");
|
||||
assert_eq!(
|
||||
output,
|
||||
(
|
||||
OptionalParamSegment("foo"),
|
||||
OptionalParamSegment("bar"),
|
||||
OptionalParamSegment("baz")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_complex() {
|
||||
let output = path!("/home/:id/foo/:bar/*any");
|
||||
let output = path!("/home/:id/foo/:bar/:baz?/*any");
|
||||
assert_eq!(
|
||||
output,
|
||||
(
|
||||
@@ -154,6 +175,7 @@ fn parses_complex() {
|
||||
ParamSegment("id"),
|
||||
StaticSegment("foo"),
|
||||
ParamSegment("bar"),
|
||||
OptionalParamSegment("baz"),
|
||||
WildcardSegment("any"),
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user