create redirects
This commit is contained in:
1 parent
87794b5039
commit
58b74c1155
9 files changed
+307
-419
No files matched your search
@@ -4,6 +4,7 @@ use layout::Layout;
|
|||||||
use nest::{Nest, NestId};
|
use nest::{Nest, NestId};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{__private::Span, format_ident, quote, ToTokens};
|
use quote::{__private::Span, format_ident, quote, ToTokens};
|
||||||
|
use redirect::Redirect;
|
||||||
use route::Route;
|
use route::Route;
|
||||||
use segment::RouteSegment;
|
use segment::RouteSegment;
|
||||||
use syn::{parse::ParseStream, parse_macro_input, Ident, Token};
|
use syn::{parse::ParseStream, parse_macro_input, Ident, Token};
|
||||||
@@ -13,14 +14,17 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||||||
use crate::{layout::LayoutId, route_tree::RouteTree};
|
use crate::{layout::LayoutId, route_tree::RouteTree};
|
||||||
|
|
||||||
mod layout;
|
mod layout;
|
||||||
mod macro2;
|
|
||||||
mod nest;
|
mod nest;
|
||||||
mod query;
|
mod query;
|
||||||
|
mod redirect;
|
||||||
mod route;
|
mod route;
|
||||||
mod route_tree;
|
mod route_tree;
|
||||||
mod segment;
|
mod segment;
|
||||||
|
|
||||||
#[proc_macro_derive(Routable)]
|
#[proc_macro_derive(
|
||||||
|
Routable,
|
||||||
|
attributes(route, nest, end_nest, layout, end_layout, redirect)
|
||||||
|
)]
|
||||||
pub fn routable(input: TokenStream) -> TokenStream {
|
pub fn routable(input: TokenStream) -> TokenStream {
|
||||||
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
|||||||
struct RouteEnum {
|
struct RouteEnum {
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
|
redirects: Vec<Redirect>,
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
nests: Vec<Nest>,
|
nests: Vec<Nest>,
|
||||||
layouts: Vec<Layout>,
|
layouts: Vec<Layout>,
|
||||||
@@ -85,6 +90,8 @@ impl RouteEnum {
|
|||||||
|
|
||||||
let mut routes = Vec::new();
|
let mut routes = Vec::new();
|
||||||
|
|
||||||
|
let mut redirects = Vec::new();
|
||||||
|
|
||||||
let mut layouts: Vec<Layout> = Vec::new();
|
let mut layouts: Vec<Layout> = Vec::new();
|
||||||
let mut layout_stack = Vec::new();
|
let mut layout_stack = Vec::new();
|
||||||
|
|
||||||
@@ -197,6 +204,16 @@ impl RouteEnum {
|
|||||||
}
|
}
|
||||||
} else if attr.path.is_ident("end_layout") {
|
} else if attr.path.is_ident("end_layout") {
|
||||||
layout_stack.pop();
|
layout_stack.pop();
|
||||||
|
} else if attr.path.is_ident("redirect") {
|
||||||
|
let parser = |input: ParseStream| {
|
||||||
|
Redirect::parse(
|
||||||
|
input,
|
||||||
|
nest_stack.iter().rev().cloned().collect(),
|
||||||
|
redirects.len(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let redirect = attr.parse_args_with(parser)?;
|
||||||
|
redirects.push(redirect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +242,7 @@ impl RouteEnum {
|
|||||||
vis: vis.clone(),
|
vis: vis.clone(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
routes,
|
routes,
|
||||||
|
redirects,
|
||||||
nests,
|
nests,
|
||||||
layouts,
|
layouts,
|
||||||
site_map,
|
site_map,
|
||||||
@@ -256,7 +274,7 @@ impl RouteEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_impl(&self) -> TokenStream2 {
|
fn parse_impl(&self) -> TokenStream2 {
|
||||||
let tree = RouteTree::new(&self.routes, &self.nests);
|
let tree = RouteTree::new(&self.routes, &self.nests, &self.redirects);
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
|
||||||
let error_name = format_ident!("{}MatchError", self.name);
|
let error_name = format_ident!("{}MatchError", self.name);
|
||||||
@@ -325,6 +343,16 @@ impl RouteEnum {
|
|||||||
type_defs.push(nest.error_type());
|
type_defs.push(nest.error_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for redirect in &self.redirects {
|
||||||
|
let error_variant = redirect.error_variant();
|
||||||
|
let error_name = redirect.error_ident();
|
||||||
|
let route_str = &redirect.route;
|
||||||
|
|
||||||
|
error_variants.push(quote! { #error_variant(#error_name) });
|
||||||
|
display_match.push(quote! { Self::#error_variant(err) => write!(f, "Redirect '{}' ('{}') did not match:\n{}", stringify!(#error_name), #route_str, err)? });
|
||||||
|
type_defs.push(redirect.error_type());
|
||||||
|
}
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#(#type_defs)*
|
#(#type_defs)*
|
||||||
|
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
use syn::{braced, parenthesized, parse::Parse, Expr, Ident, LitStr, Path, Token};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses() {
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
let tokens = quote! {
|
|
||||||
// The name of the enum
|
|
||||||
Route,
|
|
||||||
// All nests that have dynamic segments must have a name used to generate the enum
|
|
||||||
route(User, "user" / user_id: usize) {
|
|
||||||
route(Product, "product" / product_id: usize / dynamic: usize ) {
|
|
||||||
// Render creates a new route (that will be included in the enum) and is rendered with the given component
|
|
||||||
// The component uses the struct of the parent route as a prop (in this case, Product)
|
|
||||||
render(Other)
|
|
||||||
}
|
|
||||||
|
|
||||||
// You can nest routes inside a layout to wrap them in a component that accepts the struct of the parent route as a prop (in this case, User)
|
|
||||||
layout(UserFrame) {
|
|
||||||
route(Route1Props, "hello_world" / dynamic: usize ) {
|
|
||||||
// (Accepts Route1Props as a prop)
|
|
||||||
render(Route1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// You can opt out of the layout by using !layout
|
|
||||||
!layout(UserFrame) {
|
|
||||||
route(Route2Props, "hello_world" / dynamic: usize ) {
|
|
||||||
// (Accepts Route2Props as a prop)
|
|
||||||
render(Route2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route(Route3Props, "hello_world" / dynamic: usize ) {
|
|
||||||
// (Accepts Route3Props as a prop)
|
|
||||||
render(Route3)
|
|
||||||
}
|
|
||||||
|
|
||||||
route(RedirectData, dynamic: usize / extra: String) {
|
|
||||||
// Redirects accept a function that receives the struct of the parent route and returns the new route
|
|
||||||
redirect(|data: RedirectData| todo!() )
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = syn::parse2::<RouteTree>(tokens).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RouteTree {
|
|
||||||
name: Ident,
|
|
||||||
roots: Vec<RouteSegment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for RouteTree {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let name = input.parse()?;
|
|
||||||
let _ = input.parse::<Token![,]>();
|
|
||||||
|
|
||||||
let mut roots = Vec::new();
|
|
||||||
while !input.is_empty() {
|
|
||||||
roots.push(input.parse()?);
|
|
||||||
}
|
|
||||||
Ok(Self { name, roots })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RouteSegment {
|
|
||||||
Route(Route),
|
|
||||||
Layout(Layout),
|
|
||||||
Render(Render),
|
|
||||||
Redirect(Redirect),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for RouteSegment {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let lookahead = input.lookahead1();
|
|
||||||
if lookahead.peek(syn::Token![!]) {
|
|
||||||
input.parse::<Token![!]>()?;
|
|
||||||
let ident: Ident = input.parse()?;
|
|
||||||
if ident == "layout" {
|
|
||||||
let mut layout: Layout = input.parse()?;
|
|
||||||
layout.opt_out = true;
|
|
||||||
Ok(RouteSegment::Layout(layout))
|
|
||||||
} else {
|
|
||||||
Err(lookahead.error())
|
|
||||||
}
|
|
||||||
} else if lookahead.peek(syn::Ident) {
|
|
||||||
let ident: Ident = input.parse()?;
|
|
||||||
if ident == "route" {
|
|
||||||
let route = input.parse()?;
|
|
||||||
Ok(RouteSegment::Route(route))
|
|
||||||
} else if ident == "layout" {
|
|
||||||
let layout = input.parse()?;
|
|
||||||
Ok(RouteSegment::Layout(layout))
|
|
||||||
} else if ident == "render" {
|
|
||||||
let render = input.parse()?;
|
|
||||||
Ok(RouteSegment::Render(render))
|
|
||||||
} else if ident == "redirect" {
|
|
||||||
let redirect = input.parse()?;
|
|
||||||
Ok(RouteSegment::Redirect(redirect))
|
|
||||||
} else {
|
|
||||||
Err(lookahead.error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(lookahead.error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Render {
|
|
||||||
component: Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Render {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let inner;
|
|
||||||
parenthesized!(inner in input);
|
|
||||||
let component = inner.parse()?;
|
|
||||||
|
|
||||||
Ok(Self { component })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Layout {
|
|
||||||
opt_out: bool,
|
|
||||||
component: Path,
|
|
||||||
children: Vec<RouteSegment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Layout {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let inner;
|
|
||||||
parenthesized!(inner in input);
|
|
||||||
let component = inner.parse()?;
|
|
||||||
|
|
||||||
let content;
|
|
||||||
braced!(content in input);
|
|
||||||
let mut children = Vec::new();
|
|
||||||
while !content.is_empty() {
|
|
||||||
children.push(content.parse()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
opt_out: false,
|
|
||||||
component,
|
|
||||||
children,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Route {
|
|
||||||
name: Ident,
|
|
||||||
path: RoutePath,
|
|
||||||
children: Vec<RouteSegment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Route {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let inner;
|
|
||||||
parenthesized!(inner in input);
|
|
||||||
let name = inner.parse()?;
|
|
||||||
inner.parse::<Token![,]>()?;
|
|
||||||
let path = inner.parse()?;
|
|
||||||
|
|
||||||
let content;
|
|
||||||
braced!(content in input);
|
|
||||||
let mut children = Vec::new();
|
|
||||||
while !content.is_empty() {
|
|
||||||
children.push(content.parse()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
children,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RoutePath {
|
|
||||||
segments: Vec<RoutePathSegment>,
|
|
||||||
query: Option<QuerySegment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for RoutePath {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
// parse all segments first
|
|
||||||
let mut segments = Vec::new();
|
|
||||||
// remove any leading slash
|
|
||||||
if input.peek(syn::Token![/]) {
|
|
||||||
input.parse::<syn::Token![/]>()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
while !input.is_empty() {
|
|
||||||
let peak = input.lookahead1();
|
|
||||||
// check if the next segment is a query
|
|
||||||
if peak.peek(syn::Token![?]) {
|
|
||||||
break;
|
|
||||||
} else if peak.peek(syn::Token![/]) {
|
|
||||||
input.parse::<syn::Token![/]>()?;
|
|
||||||
} else if peak.peek(syn::Ident) || peak.peek(syn::Token![...]) || peak.peek(syn::LitStr)
|
|
||||||
{
|
|
||||||
// parse the segment
|
|
||||||
segments.push(input.parse()?);
|
|
||||||
} else {
|
|
||||||
return Err(peak.error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// then parse the query
|
|
||||||
let query = if input.peek(syn::Token![?]) {
|
|
||||||
Some(input.parse()?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Ok(Self { segments, query })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RoutePathSegment {
|
|
||||||
Static(String),
|
|
||||||
Dynamic(Ident, Path),
|
|
||||||
CatchAll(Ident, Path),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for RoutePathSegment {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let lookahead = input.lookahead1();
|
|
||||||
if lookahead.peek(Token![...]) {
|
|
||||||
input.parse::<Token![...]>()?;
|
|
||||||
let name: Ident = input.parse()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
let type_: Path = input.parse()?;
|
|
||||||
|
|
||||||
// parse the /
|
|
||||||
let _ = input.parse::<Token![/]>();
|
|
||||||
|
|
||||||
Ok(RoutePathSegment::CatchAll(name, type_))
|
|
||||||
} else if lookahead.peek(LitStr) {
|
|
||||||
let lit: LitStr = input.parse()?;
|
|
||||||
|
|
||||||
// parse the /
|
|
||||||
let _ = input.parse::<Token![/]>();
|
|
||||||
|
|
||||||
Ok(RoutePathSegment::Static(lit.value()))
|
|
||||||
} else if lookahead.peek(Ident) {
|
|
||||||
let ident: Ident = input.parse()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
let type_: Path = input.parse()?;
|
|
||||||
|
|
||||||
// parse the /
|
|
||||||
let _ = input.parse::<Token![/]>();
|
|
||||||
|
|
||||||
Ok(RoutePathSegment::Dynamic(ident, type_))
|
|
||||||
} else {
|
|
||||||
Err(lookahead.error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct QuerySegment {
|
|
||||||
name: Ident,
|
|
||||||
type_: Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for QuerySegment {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
input.parse::<syn::Token![?]>()?;
|
|
||||||
let name = input.parse()?;
|
|
||||||
input.parse::<syn::Token![:]>()?;
|
|
||||||
let type_ = input.parse()?;
|
|
||||||
Ok(Self { name, type_ })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Redirect {
|
|
||||||
function: Expr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Redirect {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let inner;
|
|
||||||
parenthesized!(inner in input);
|
|
||||||
let function = inner.parse()?;
|
|
||||||
Ok(Self { function })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{Ident, LitStr};
|
use syn::{Ident, LitStr};
|
||||||
|
|
||||||
use crate::segment::{parse_route_segments, RouteSegment};
|
use crate::segment::{create_error_type, parse_route_segments, RouteSegment};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct NestId(pub usize);
|
pub struct NestId(pub usize);
|
||||||
@@ -25,7 +25,10 @@ impl Nest {
|
|||||||
|
|
||||||
let route_segments = parse_route_segments(
|
let route_segments = parse_route_segments(
|
||||||
route.span(),
|
route.span(),
|
||||||
children_routes.iter().flat_map(|f| f.named.iter()),
|
children_routes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|f| f.named.iter())
|
||||||
|
.map(|f| (f.ident.as_ref().unwrap(), &f.ty)),
|
||||||
&route.value(),
|
&route.value(),
|
||||||
)?
|
)?
|
||||||
.0;
|
.0;
|
||||||
@@ -79,42 +82,6 @@ impl Nest {
|
|||||||
pub fn error_type(&self) -> TokenStream {
|
pub fn error_type(&self) -> TokenStream {
|
||||||
let error_name = self.error_ident();
|
let error_name = self.error_ident();
|
||||||
|
|
||||||
let mut error_variants = Vec::new();
|
create_error_type(error_name, &self.segments)
|
||||||
let mut display_match = Vec::new();
|
|
||||||
|
|
||||||
for (i, segment) in self.segments.iter().enumerate() {
|
|
||||||
let error_name = segment.error_name(i);
|
|
||||||
match segment {
|
|
||||||
RouteSegment::Static(index) => {
|
|
||||||
error_variants.push(quote! { #error_name });
|
|
||||||
display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
|
|
||||||
}
|
|
||||||
RouteSegment::Dynamic(ident, ty) => {
|
|
||||||
let missing_error = segment.missing_error_name().unwrap();
|
|
||||||
error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
|
|
||||||
display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
|
||||||
error_variants.push(quote! { #missing_error });
|
|
||||||
display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum #error_name {
|
|
||||||
#(#error_variants,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for #error_name {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
#(#display_match,)*
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::LitStr;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
nest::NestId,
|
||||||
|
query::QuerySegment,
|
||||||
|
segment::{create_error_type, parse_route_segments, RouteSegment},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Redirect {
|
||||||
|
pub route: LitStr,
|
||||||
|
pub nests: Vec<NestId>,
|
||||||
|
pub segments: Vec<RouteSegment>,
|
||||||
|
pub query: Option<QuerySegment>,
|
||||||
|
pub function: syn::ExprClosure,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Redirect {
|
||||||
|
pub fn error_ident(&self) -> Ident {
|
||||||
|
format_ident!("Redirect{}ParseError", self.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_variant(&self) -> Ident {
|
||||||
|
format_ident!("Redirect{}", self.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_type(&self) -> TokenStream {
|
||||||
|
let error_name = self.error_ident();
|
||||||
|
|
||||||
|
create_error_type(error_name, &self.segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_query(&self) -> TokenStream {
|
||||||
|
match &self.query {
|
||||||
|
Some(query) => query.parse(),
|
||||||
|
None => quote! {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(
|
||||||
|
input: syn::parse::ParseStream,
|
||||||
|
active_nests: Vec<NestId>,
|
||||||
|
index: usize,
|
||||||
|
) -> syn::Result<Self> {
|
||||||
|
let path = input.parse::<syn::LitStr>()?;
|
||||||
|
|
||||||
|
let _ = input.parse::<syn::Token![,]>();
|
||||||
|
let function = input.parse::<syn::ExprClosure>()?;
|
||||||
|
|
||||||
|
let mut closure_arguments = Vec::new();
|
||||||
|
for arg in function.inputs.iter() {
|
||||||
|
match arg {
|
||||||
|
syn::Pat::Type(pat) => match &*pat.pat {
|
||||||
|
syn::Pat::Ident(ident) => {
|
||||||
|
closure_arguments.push((ident.ident.clone(), (*pat.ty).clone()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
arg,
|
||||||
|
"Expected closure argument to be a typed pattern",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
arg,
|
||||||
|
"Expected closure argument to be a typed pattern",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (segments, query) = parse_route_segments(
|
||||||
|
path.span(),
|
||||||
|
closure_arguments.iter().map(|(name, ty)| (name, ty)),
|
||||||
|
&path.value(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Redirect {
|
||||||
|
route: path,
|
||||||
|
nests: active_nests,
|
||||||
|
segments,
|
||||||
|
query,
|
||||||
|
function,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use crate::layout::LayoutId;
|
|||||||
use crate::nest::Nest;
|
use crate::nest::Nest;
|
||||||
use crate::nest::NestId;
|
use crate::nest::NestId;
|
||||||
use crate::query::QuerySegment;
|
use crate::query::QuerySegment;
|
||||||
|
use crate::segment::create_error_type;
|
||||||
use crate::segment::parse_route_segments;
|
use crate::segment::parse_route_segments;
|
||||||
use crate::segment::RouteSegment;
|
use crate::segment::RouteSegment;
|
||||||
|
|
||||||
@@ -90,8 +91,14 @@ impl Route {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (route_segments, query) =
|
let (route_segments, query) = parse_route_segments(
|
||||||
parse_route_segments(variant.ident.span(), named_fields.named.iter(), &route)?;
|
variant.ident.span(),
|
||||||
|
named_fields
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(|f| (f.ident.as_ref().unwrap(), &f.ty)),
|
||||||
|
&route,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
comp_name,
|
comp_name,
|
||||||
@@ -217,50 +224,7 @@ impl Route {
|
|||||||
pub fn error_type(&self) -> TokenStream2 {
|
pub fn error_type(&self) -> TokenStream2 {
|
||||||
let error_name = self.error_ident();
|
let error_name = self.error_ident();
|
||||||
|
|
||||||
let mut error_variants = Vec::new();
|
create_error_type(error_name, &self.segments)
|
||||||
let mut display_match = Vec::new();
|
|
||||||
|
|
||||||
for (i, segment) in self.segments.iter().enumerate() {
|
|
||||||
let error_name = segment.error_name(i);
|
|
||||||
match segment {
|
|
||||||
RouteSegment::Static(index) => {
|
|
||||||
error_variants.push(quote! { #error_name });
|
|
||||||
display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
|
|
||||||
}
|
|
||||||
RouteSegment::Dynamic(ident, ty) => {
|
|
||||||
let missing_error = segment.missing_error_name().unwrap();
|
|
||||||
error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
|
|
||||||
display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
|
||||||
error_variants.push(quote! { #missing_error });
|
|
||||||
display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
|
|
||||||
}
|
|
||||||
RouteSegment::CatchAll(ident, ty) => {
|
|
||||||
error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
|
|
||||||
display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum #error_name {
|
|
||||||
ExtraSegments(String),
|
|
||||||
#(#error_variants,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for #error_name {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::ExtraSegments(segments) => {
|
|
||||||
write!(f, "Found additional trailing segments: {}", segments)?
|
|
||||||
}
|
|
||||||
#(#display_match,)*
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_query(&self) -> TokenStream2 {
|
pub fn parse_query(&self) -> TokenStream2 {
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use slab::Slab;
|
|||||||
use syn::Ident;
|
use syn::Ident;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
nest::Nest,
|
nest::{Nest, NestId},
|
||||||
|
redirect::Redirect,
|
||||||
route::Route,
|
route::Route,
|
||||||
segment::{static_segment_idx, RouteSegment},
|
segment::{static_segment_idx, RouteSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct RouteTree<'a> {
|
pub(crate) struct RouteTree<'a> {
|
||||||
pub roots: Vec<usize>,
|
pub roots: Vec<usize>,
|
||||||
entries: Slab<RouteTreeSegmentData<'a>>,
|
entries: Slab<RouteTreeSegmentData<'a>>,
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,13 @@ impl<'a> RouteTree<'a> {
|
|||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RouteTreeSegmentData::Redirect(redirect) => {
|
||||||
|
// Routes that end in a catch all segment should be checked last
|
||||||
|
match redirect.segments.last() {
|
||||||
|
Some(RouteSegment::CatchAll(..)) => 2,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,10 +97,15 @@ impl<'a> RouteTree<'a> {
|
|||||||
.expect("Cannot get children of non static or nest segment")
|
.expect("Cannot get children of non static or nest segment")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(routes: &'a [Route], nests: &'a [Nest]) -> Self {
|
pub(crate) fn new(routes: &'a [Route], nests: &'a [Nest], redirects: &'a [Redirect]) -> Self {
|
||||||
let routes = routes
|
let routes = routes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|route| RouteIter::new(route, nests))
|
.map(|route| PathIter::new_route(route, nests))
|
||||||
|
.chain(
|
||||||
|
redirects
|
||||||
|
.iter()
|
||||||
|
.map(|redirect| PathIter::new_redirect(redirect, nests)),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut myself = Self::default();
|
let mut myself = Self::default();
|
||||||
@@ -102,7 +115,7 @@ impl<'a> RouteTree<'a> {
|
|||||||
myself
|
myself
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn construct(&mut self, routes: Vec<RouteIter<'a>>) -> Vec<usize> {
|
pub fn construct(&mut self, routes: Vec<PathIter<'a>>) -> Vec<usize> {
|
||||||
let mut segments = Vec::new();
|
let mut segments = Vec::new();
|
||||||
|
|
||||||
// Add all routes to the tree
|
// Add all routes to the tree
|
||||||
@@ -127,7 +140,7 @@ impl<'a> RouteTree<'a> {
|
|||||||
for &seg_id in segments.iter() {
|
for &seg_id in segments.iter() {
|
||||||
let seg = self.get(seg_id).unwrap();
|
let seg = self.get(seg_id).unwrap();
|
||||||
if let RouteTreeSegmentData::Static { segment: s, .. } = seg {
|
if let RouteTreeSegmentData::Static { segment: s, .. } = seg {
|
||||||
if s == segment {
|
if *s == segment {
|
||||||
// If it does, just update the current route
|
// If it does, just update the current route
|
||||||
current_route = Some(seg_id);
|
current_route = Some(seg_id);
|
||||||
continue 'o;
|
continue 'o;
|
||||||
@@ -223,9 +236,7 @@ impl<'a> RouteTree<'a> {
|
|||||||
}
|
}
|
||||||
// If there is no static segment, add the route to the current_route
|
// If there is no static segment, add the route to the current_route
|
||||||
None => {
|
None => {
|
||||||
let id = self
|
let id = self.entries.insert(route.final_segment);
|
||||||
.entries
|
|
||||||
.insert(RouteTreeSegmentData::Route(route.route));
|
|
||||||
let current_children_mut = current_route
|
let current_children_mut = current_route
|
||||||
.map(|id| self.children_mut(id))
|
.map(|id| self.children_mut(id))
|
||||||
.unwrap_or_else(|| &mut segments);
|
.unwrap_or_else(|| &mut segments);
|
||||||
@@ -246,7 +257,7 @@ pub struct StaticErrorVariant {
|
|||||||
|
|
||||||
// First deduplicate the routes by the static part of the route
|
// First deduplicate the routes by the static part of the route
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RouteTreeSegmentData<'a> {
|
pub(crate) enum RouteTreeSegmentData<'a> {
|
||||||
Static {
|
Static {
|
||||||
segment: &'a str,
|
segment: &'a str,
|
||||||
error_variant: StaticErrorVariant,
|
error_variant: StaticErrorVariant,
|
||||||
@@ -258,6 +269,7 @@ pub enum RouteTreeSegmentData<'a> {
|
|||||||
children: Vec<usize>,
|
children: Vec<usize>,
|
||||||
},
|
},
|
||||||
Route(&'a Route),
|
Route(&'a Route),
|
||||||
|
Redirect(&'a Redirect),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RouteTreeSegmentData<'a> {
|
impl<'a> RouteTreeSegmentData<'a> {
|
||||||
@@ -362,6 +374,52 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||||||
&varient_parse_error,
|
&varient_parse_error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Self::Redirect(redirect) => {
|
||||||
|
// At this point, we have matched all static segments, so we can just check if the remaining segments match the route
|
||||||
|
let varient_parse_error = redirect.error_ident();
|
||||||
|
let enum_varient = &redirect.error_variant();
|
||||||
|
|
||||||
|
let route_segments = redirect
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
|
||||||
|
|
||||||
|
let parse_query = redirect.parse_query();
|
||||||
|
|
||||||
|
let insure_not_trailing = redirect
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
let redirect_function = &redirect.function;
|
||||||
|
let args = redirect_function.inputs.iter().map(|pat| match pat {
|
||||||
|
syn::Pat::Type(ident) => {
|
||||||
|
let name = &ident.pat;
|
||||||
|
quote! {#name}
|
||||||
|
}
|
||||||
|
_ => panic!("Expected closure argument to be a typed pattern"),
|
||||||
|
});
|
||||||
|
let return_redirect = quote! {
|
||||||
|
(#redirect_function)(#(#args,)*)
|
||||||
|
};
|
||||||
|
|
||||||
|
print_route_segment(
|
||||||
|
route_segments.peekable(),
|
||||||
|
return_constructed(
|
||||||
|
insure_not_trailing,
|
||||||
|
return_redirect,
|
||||||
|
&error_enum_name,
|
||||||
|
enum_varient,
|
||||||
|
&varient_parse_error,
|
||||||
|
parse_query,
|
||||||
|
),
|
||||||
|
&error_enum_name,
|
||||||
|
enum_varient,
|
||||||
|
&varient_parse_error,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,18 +493,39 @@ fn return_constructed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RouteIter<'a> {
|
pub struct PathIter<'a> {
|
||||||
route: &'a Route,
|
final_segment: RouteTreeSegmentData<'a>,
|
||||||
nests: &'a [Nest],
|
active_nests: &'a [NestId],
|
||||||
|
all_nests: &'a [Nest],
|
||||||
|
segments: &'a [RouteSegment],
|
||||||
|
error_ident: Ident,
|
||||||
|
error_variant: Ident,
|
||||||
nest_index: usize,
|
nest_index: usize,
|
||||||
static_segment_index: usize,
|
static_segment_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RouteIter<'a> {
|
impl<'a> PathIter<'a> {
|
||||||
fn new(route: &'a Route, nests: &'a [Nest]) -> Self {
|
fn new_route(route: &'a Route, nests: &'a [Nest]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
route,
|
final_segment: RouteTreeSegmentData::Route(route),
|
||||||
nests,
|
active_nests: &*route.nests,
|
||||||
|
segments: &*route.segments,
|
||||||
|
error_ident: route.error_ident(),
|
||||||
|
error_variant: route.route_name.clone(),
|
||||||
|
all_nests: nests,
|
||||||
|
nest_index: 0,
|
||||||
|
static_segment_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_redirect(redirect: &'a Redirect, nests: &'a [Nest]) -> Self {
|
||||||
|
Self {
|
||||||
|
final_segment: RouteTreeSegmentData::Redirect(redirect),
|
||||||
|
active_nests: &*redirect.nests,
|
||||||
|
segments: &*redirect.segments,
|
||||||
|
error_ident: redirect.error_ident(),
|
||||||
|
error_variant: redirect.error_variant(),
|
||||||
|
all_nests: nests,
|
||||||
nest_index: 0,
|
nest_index: 0,
|
||||||
static_segment_index: 0,
|
static_segment_index: 0,
|
||||||
}
|
}
|
||||||
@@ -454,15 +533,15 @@ impl<'a> RouteIter<'a> {
|
|||||||
|
|
||||||
fn next_nest(&mut self) -> Option<&'a Nest> {
|
fn next_nest(&mut self) -> Option<&'a Nest> {
|
||||||
let idx = self.nest_index;
|
let idx = self.nest_index;
|
||||||
let nest_index = self.route.nests.get(idx)?;
|
let nest_index = self.active_nests.get(idx)?;
|
||||||
let nest = &self.nests[nest_index.0];
|
let nest = &self.all_nests[nest_index.0];
|
||||||
self.nest_index += 1;
|
self.nest_index += 1;
|
||||||
Some(nest)
|
Some(nest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {
|
fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {
|
||||||
let idx = self.static_segment_index;
|
let idx = self.static_segment_index;
|
||||||
let segment = self.route.segments.get(idx)?;
|
let segment = self.segments.get(idx)?;
|
||||||
match segment {
|
match segment {
|
||||||
RouteSegment::Static(segment) => {
|
RouteSegment::Static(segment) => {
|
||||||
self.static_segment_index += 1;
|
self.static_segment_index += 1;
|
||||||
@@ -474,8 +553,8 @@ impl<'a> RouteIter<'a> {
|
|||||||
|
|
||||||
fn error_variant(&self) -> StaticErrorVariant {
|
fn error_variant(&self) -> StaticErrorVariant {
|
||||||
StaticErrorVariant {
|
StaticErrorVariant {
|
||||||
varient_parse_error: self.route.error_ident(),
|
varient_parse_error: self.error_ident.clone(),
|
||||||
enum_varient: self.route.route_name.clone(),
|
enum_varient: self.error_variant.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ pub fn static_segment_idx(idx: usize) -> Ident {
|
|||||||
|
|
||||||
pub fn parse_route_segments<'a>(
|
pub fn parse_route_segments<'a>(
|
||||||
route_span: Span,
|
route_span: Span,
|
||||||
mut fields: impl Iterator<Item = &'a syn::Field>,
|
mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,
|
||||||
route: &str,
|
route: &str,
|
||||||
) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
|
) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
|
||||||
let mut route_segments = Vec::new();
|
let mut route_segments = Vec::new();
|
||||||
@@ -157,13 +157,10 @@ pub fn parse_route_segments<'a>(
|
|||||||
segment.to_string()
|
segment.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let field = fields.find(|field| match field.ident {
|
let field = fields.find(|(name, _)| **name == ident);
|
||||||
Some(ref field_ident) => *field_ident == ident,
|
|
||||||
None => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let ty = if let Some(field) = field {
|
let ty = if let Some(field) = field {
|
||||||
field.ty.clone()
|
field.1.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
route_span,
|
route_span,
|
||||||
@@ -200,13 +197,10 @@ pub fn parse_route_segments<'a>(
|
|||||||
Some(query) => {
|
Some(query) => {
|
||||||
if let Some(query) = query.strip_prefix(':') {
|
if let Some(query) = query.strip_prefix(':') {
|
||||||
let query_ident = Ident::new(query, Span::call_site());
|
let query_ident = Ident::new(query, Span::call_site());
|
||||||
let field = fields.find(|field| match field.ident {
|
let field = fields.find(|(name, _)| *name == &query_ident);
|
||||||
Some(ref field_ident) => field_ident == &query_ident,
|
|
||||||
None => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let ty = if let Some(field) = field {
|
let ty = if let Some((_, ty)) = field {
|
||||||
field.ty.clone()
|
ty.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
route_span,
|
route_span,
|
||||||
@@ -227,3 +221,52 @@ pub fn parse_route_segments<'a>(
|
|||||||
|
|
||||||
Ok((route_segments, parsed_query))
|
Ok((route_segments, parsed_query))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_error_type(error_name: Ident, segments: &[RouteSegment]) -> TokenStream2 {
|
||||||
|
let mut error_variants = Vec::new();
|
||||||
|
let mut display_match = Vec::new();
|
||||||
|
|
||||||
|
for (i, segment) in segments.iter().enumerate() {
|
||||||
|
let error_name = segment.error_name(i);
|
||||||
|
match segment {
|
||||||
|
RouteSegment::Static(index) => {
|
||||||
|
error_variants.push(quote! { #error_name });
|
||||||
|
display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
|
||||||
|
}
|
||||||
|
RouteSegment::Dynamic(ident, ty) => {
|
||||||
|
let missing_error = segment.missing_error_name().unwrap();
|
||||||
|
error_variants.push(
|
||||||
|
quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) },
|
||||||
|
);
|
||||||
|
display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
||||||
|
error_variants.push(quote! { #missing_error });
|
||||||
|
display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
|
||||||
|
}
|
||||||
|
RouteSegment::CatchAll(ident, ty) => {
|
||||||
|
error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
|
||||||
|
display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum #error_name {
|
||||||
|
ExtraSegments(String),
|
||||||
|
#(#error_variants,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for #error_name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::ExtraSegments(segments) => {
|
||||||
|
write!(f, "Found additional trailing segments: {}", segments)?
|
||||||
|
}
|
||||||
|
#(#display_match,)*
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,13 +45,13 @@ fn UserFrame(cx: Scope, user_id: usize) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline_props]
|
#[inline_props]
|
||||||
fn Route1(cx: Scope, user_id: usize, dynamic: usize, extra: String) -> Element {
|
fn Route1(cx: Scope, user_id: usize, dynamic: usize, query: String, extra: String) -> Element {
|
||||||
render! {
|
render! {
|
||||||
pre {
|
pre {
|
||||||
"Route1{{\n\tuser_id:{user_id},\n\tdynamic:{dynamic},\n\textra:{extra}\n}}"
|
"Route1{{\n\tuser_id:{user_id},\n\tdynamic:{dynamic},\n\tquery:{query},\n\textra:{extra}\n}}"
|
||||||
}
|
}
|
||||||
Link {
|
Link {
|
||||||
target: Route::Route1 { user_id: *user_id, dynamic: *dynamic, extra: extra.clone() + "." },
|
target: Route::Route1 { user_id: *user_id, dynamic: *dynamic, query: String::new(), extra: extra.clone() + "." },
|
||||||
"Route1 with extra+\".\""
|
"Route1 with extra+\".\""
|
||||||
}
|
}
|
||||||
p { "Footer" }
|
p { "Footer" }
|
||||||
@@ -131,12 +131,13 @@ enum Route {
|
|||||||
// Everything inside the nest has the added parameter `user_id: String`
|
// Everything inside the nest has the added parameter `user_id: String`
|
||||||
// UserFrame is a layout component that will receive the `user_id: String` parameter
|
// UserFrame is a layout component that will receive the `user_id: String` parameter
|
||||||
#[layout(UserFrame)]
|
#[layout(UserFrame)]
|
||||||
// Route1 is a non-layout component that will receive the `user_id: String` and `dynamic: String` parameters
|
// Route1 is a non-layout component that will receive the `user_id: String` and `dynamic: String` parameters
|
||||||
#[route("/:dynamic", Route1)]
|
#[route("/:dynamic?:query", Route1)]
|
||||||
Route1 {
|
Route1 {
|
||||||
// The type is taken from the first instance of the dynamic parameter
|
// The type is taken from the first instance of the dynamic parameter
|
||||||
user_id: usize,
|
user_id: usize,
|
||||||
dynamic: usize,
|
dynamic: usize,
|
||||||
|
query: String,
|
||||||
extra: String,
|
extra: String,
|
||||||
},
|
},
|
||||||
// Route2 is a non-layout component that will receive the `user_id: String` parameter
|
// Route2 is a non-layout component that will receive the `user_id: String` parameter
|
||||||
@@ -146,6 +147,7 @@ enum Route {
|
|||||||
Route2 { user_id: usize },
|
Route2 { user_id: usize },
|
||||||
#[end_layout]
|
#[end_layout]
|
||||||
#[end_nest]
|
#[end_nest]
|
||||||
|
#[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})]
|
||||||
#[route("/:dynamic", Route3)]
|
#[route("/:dynamic", Route3)]
|
||||||
Route3 { dynamic: String },
|
Route3 { dynamic: String },
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ use super::{
|
|||||||
HistoryProvider,
|
HistoryProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn update_scroll<R: Serialize + DeserializeOwned>(window: &Window, history: &History) {
|
fn update_scroll<R: Serialize + DeserializeOwned + Routable>(window: &Window, history: &History) {
|
||||||
if let Some(WebHistoryState { state, .. }) = get_current::<WebHistoryState<R>>(history) {
|
if let Some(WebHistoryState { state, .. }) = get_current::<WebHistoryState<R>>(history) {
|
||||||
let scroll = ScrollPosition::of_window(window);
|
let scroll = ScrollPosition::of_window(window);
|
||||||
let state = WebHistoryState { state, scroll };
|
let state = WebHistoryState { state, scroll };
|
||||||
@@ -42,7 +42,7 @@ struct WebHistoryState<R> {
|
|||||||
/// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
|
/// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
|
||||||
///
|
///
|
||||||
/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
|
/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
|
||||||
pub struct WebHistory<R: Serialize + DeserializeOwned> {
|
pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
|
||||||
do_scroll_restoration: bool,
|
do_scroll_restoration: bool,
|
||||||
history: History,
|
history: History,
|
||||||
listener_navigation: Option<EventListener>,
|
listener_navigation: Option<EventListener>,
|
||||||
@@ -102,10 +102,10 @@ impl<R: Serialize + DeserializeOwned + Routable> WebHistory<R> {
|
|||||||
phantom: Default::default(),
|
phantom: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = myself.current_route();
|
let current_route = myself.current_route();
|
||||||
|
let current_url = current_route.to_string();
|
||||||
let state = myself.create_state(state);
|
let state = myself.create_state(current_route);
|
||||||
let _ = replace_state_with_url(&myself.history, &state, None);
|
let _ = replace_state_with_url(&myself.history, &state, Some(¤t_url));
|
||||||
|
|
||||||
myself
|
myself
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user