mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
implement layouts and outlets
This commit is contained in:
parent
f4227c7311
commit
fbd333e334
10 changed files with 910 additions and 259 deletions
|
@ -1,7 +1,8 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
use crate::history::HistoryProvider;
|
use crate::history::HistoryProvider;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::{cell::RefCell, rc::Rc, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct RouteParseError<E: std::fmt::Display> {
|
pub struct RouteParseError<E: std::fmt::Display> {
|
||||||
|
@ -18,27 +19,38 @@ impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Router<R: Routable, H: HistoryProvider>
|
#[derive(Clone)]
|
||||||
where
|
pub struct Router {
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
subscribers: Rc<RefCell<Vec<ScopeId>>>,
|
||||||
{
|
update_any: Arc<dyn Fn(ScopeId)>,
|
||||||
history: H,
|
history: Rc<dyn HistoryProvider>,
|
||||||
route: R,
|
route: Rc<RefCell<Option<Rc<dyn RouteRenderable>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable, H: HistoryProvider> Router<R, H>
|
impl Router {
|
||||||
where
|
fn set_route<R: Routable + 'static>(&self, route: R)
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
where
|
||||||
{
|
R::Err: std::fmt::Display,
|
||||||
fn new(history: H) -> Result<Self, R::Err> {
|
{
|
||||||
let path = history.current_path();
|
*self.route.borrow_mut() = Some(Rc::new(route));
|
||||||
Ok(Self {
|
for subscriber in self.subscribers.borrow().iter() {
|
||||||
history,
|
(self.update_any)(*subscriber);
|
||||||
route: R::from_str(path.as_str())?,
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn use_router(cx: &ScopeState) -> &Router {
|
||||||
|
use_context(cx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_route(cx: &ScopeState) -> Rc<dyn RouteRenderable> {
|
||||||
|
let router = use_router(cx);
|
||||||
|
cx.use_hook(|| {
|
||||||
|
router.subscribers.borrow_mut().push(cx.scope_id());
|
||||||
|
});
|
||||||
|
router.route.borrow().clone().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub trait FromQuery {
|
pub trait FromQuery {
|
||||||
fn from_query(query: &str) -> Self;
|
fn from_query(query: &str) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -104,25 +116,93 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
|
||||||
pub struct RouterProps {
|
pub struct RouterProps {
|
||||||
pub current_route: String,
|
pub current_route: String,
|
||||||
}
|
}
|
||||||
|
pub trait Routable: std::fmt::Display + std::str::FromStr + 'static
|
||||||
pub trait Routable: FromStr + std::fmt::Display + Clone
|
|
||||||
where
|
where
|
||||||
<Self as FromStr>::Err: std::fmt::Display,
|
<Self as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
fn render(self, cx: &ScopeState) -> Element;
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
fn comp(cx: Scope<RouterProps>) -> Element
|
trait RoutableFactory {
|
||||||
where
|
type Err: std::fmt::Display;
|
||||||
Self: 'static,
|
type Routable: Routable + FromStr<Err = Self::Err>;
|
||||||
{
|
}
|
||||||
let router = Self::from_str(&cx.props.current_route);
|
|
||||||
match router {
|
impl<R: Routable + FromStr> RoutableFactory for R
|
||||||
Ok(router) => router.render(cx),
|
where
|
||||||
Err(err) => {
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
render! {
|
{
|
||||||
pre {
|
type Err = <R as FromStr>::Err;
|
||||||
"{err}"
|
type Routable = R;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait RouteRenderable: std::fmt::Display + 'static {
|
||||||
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Routable> RouteRenderable for R
|
||||||
|
where
|
||||||
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
|
||||||
|
self.render(cx, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct OutletContext {
|
||||||
|
current_level: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_outlet_context(cx: &ScopeState) -> &OutletContext {
|
||||||
|
let outlet_context = use_context(cx).unwrap();
|
||||||
|
outlet_context
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutletContext {
|
||||||
|
fn render(cx: &ScopeState) -> Element<'_> {
|
||||||
|
let outlet = use_outlet_context(cx);
|
||||||
|
let current_level = outlet.current_level;
|
||||||
|
cx.provide_context({
|
||||||
|
OutletContext {
|
||||||
|
current_level: current_level + 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
use_route(cx).render(cx, current_level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Outlet(cx: Scope) -> Element {
|
||||||
|
OutletContext::render(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Router<R: Routable, H: HistoryProvider + Default + 'static>(
|
||||||
|
cx: Scope<RouterProps>,
|
||||||
|
) -> Element
|
||||||
|
where
|
||||||
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let current_route = R::from_str(&cx.props.current_route);
|
||||||
|
let router = use_context_provider(cx, || Router {
|
||||||
|
subscribers: Rc::default(),
|
||||||
|
update_any: cx.schedule_update_any(),
|
||||||
|
history: Rc::<H>::default(),
|
||||||
|
route: Rc::new(RefCell::new(None)),
|
||||||
|
});
|
||||||
|
|
||||||
|
use_context_provider(cx, || OutletContext { current_level: 1 });
|
||||||
|
|
||||||
|
match current_route {
|
||||||
|
Ok(current_route) => {
|
||||||
|
router.set_route(current_route);
|
||||||
|
|
||||||
|
router.route.borrow().as_ref().unwrap().render(cx, 0)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
render! {
|
||||||
|
pre {
|
||||||
|
"{err}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,12 +59,22 @@ fn Route6(cx: Scope, extra: Vec<String>) -> Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Nested(cx: Scope, nested: String) -> Element {
|
||||||
|
render! {
|
||||||
|
div{
|
||||||
|
"Nested: {nested:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(Routable, Clone, Debug, PartialEq)]
|
#[routable]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/(dynamic)" Route1)]
|
#[route("/(dynamic)" Route1)]
|
||||||
Route1 { dynamic: String },
|
Route1 { dynamic: String },
|
||||||
#[nest("/hello_world")]
|
#[nest("/(nested)" nested { nested: String } Nested)]
|
||||||
#[route("/" Route2)]
|
#[route("/" Route2)]
|
||||||
Route2 {},
|
Route2 {},
|
||||||
// #[redirect("/(dynamic)/hello_world")]
|
// #[redirect("/(dynamic)/hello_world")]
|
||||||
|
|
35
packages/router-core/tests/nested.rs
Normal file
35
packages/router-core/tests/nested.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router_core::*;
|
||||||
|
use dioxus_router_macro::*;
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Route1(cx: Scope, dynamic: String) -> Element {
|
||||||
|
render! {
|
||||||
|
div{
|
||||||
|
"Route1: {dynamic}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Nested(cx: Scope, nested: String) -> Element {
|
||||||
|
render! {
|
||||||
|
div{
|
||||||
|
"Nested: {nested:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[routable]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum Route {
|
||||||
|
#[nest("/(nested)" nested { nested: String } Nested)]
|
||||||
|
#[route("/(dynamic)" Route1)]
|
||||||
|
Route1 { dynamic: String },
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/(dynamic)" Route1)]
|
||||||
|
Route2 { dynamic: String },
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ proc-macro = true
|
||||||
syn = { version = "1.0.11", features = ["extra-traits", "full"] }
|
syn = { version = "1.0.11", features = ["extra-traits", "full"] }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0.56"
|
proc-macro2 = "1.0.56"
|
||||||
|
slab = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use nest::Nest;
|
use nest::{Layout, Nest};
|
||||||
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 route::Route;
|
use route::Route;
|
||||||
use route_tree::RouteTreeSegment;
|
|
||||||
use syn::{parse_macro_input, Ident};
|
use syn::{parse_macro_input, Ident};
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
|
||||||
|
use crate::{nest::LayoutId, route_tree::RouteTree};
|
||||||
|
|
||||||
mod nest;
|
mod nest;
|
||||||
mod query;
|
mod query;
|
||||||
mod route;
|
mod route;
|
||||||
mod route_tree;
|
mod route_tree;
|
||||||
mod segment;
|
mod segment;
|
||||||
|
|
||||||
#[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
|
// #[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
|
||||||
pub fn derive_routable(input: TokenStream) -> TokenStream {
|
#[proc_macro_attribute]
|
||||||
let routes_enum = parse_macro_input!(input as syn::DeriveInput);
|
pub fn routable(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
||||||
|
|
||||||
let route_enum = match RouteEnum::parse(routes_enum) {
|
let route_enum = match RouteEnum::parse(routes_enum) {
|
||||||
Ok(route_enum) => route_enum,
|
Ok(route_enum) => route_enum,
|
||||||
|
@ -44,59 +46,104 @@ pub fn derive_routable(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RouteEnum {
|
struct RouteEnum {
|
||||||
route_name: Ident,
|
vis: syn::Visibility,
|
||||||
|
attrs: Vec<syn::Attribute>,
|
||||||
|
name: Ident,
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
|
layouts: Vec<Layout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteEnum {
|
impl RouteEnum {
|
||||||
fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
|
fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
|
||||||
let name = &input.ident;
|
let name = &data.ident;
|
||||||
|
|
||||||
if let syn::Data::Enum(data) = input.data {
|
enum NestRef {
|
||||||
let mut routes = Vec::new();
|
Static(String),
|
||||||
|
Dynamic { id: LayoutId },
|
||||||
|
}
|
||||||
|
|
||||||
let mut current_base_route = Vec::new();
|
let mut routes = Vec::new();
|
||||||
|
|
||||||
for variant in data.variants {
|
let mut layouts = Vec::new();
|
||||||
// Apply the any nesting attributes in order
|
|
||||||
for attr in &variant.attrs {
|
let mut nest_stack = Vec::new();
|
||||||
if attr.path.is_ident("nest") {
|
|
||||||
let nest: Nest = attr.parse_args()?;
|
for variant in data.variants {
|
||||||
match nest {
|
// Apply the any nesting attributes in order
|
||||||
Nest::Static(s) => current_base_route.push(s),
|
for attr in &variant.attrs {
|
||||||
_ => todo!(),
|
if attr.path.is_ident("nest") {
|
||||||
|
let nest: Nest = attr.parse_args()?;
|
||||||
|
let nest_ref = match nest {
|
||||||
|
Nest::Static(s) => NestRef::Static(s),
|
||||||
|
Nest::Layout(mut l) => {
|
||||||
|
// if there is a static nest before this, add it to the layout
|
||||||
|
let mut static_prefix = nest_stack
|
||||||
|
.iter()
|
||||||
|
// walk backwards and take all static nests
|
||||||
|
.rev()
|
||||||
|
.map_while(|nest| match nest {
|
||||||
|
NestRef::Static(s) => Some(s.clone()),
|
||||||
|
NestRef::Dynamic { .. } => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// reverse the static prefix so it is in the correct order
|
||||||
|
static_prefix.reverse();
|
||||||
|
|
||||||
|
if !static_prefix.is_empty() {
|
||||||
|
l.add_static_prefix(&static_prefix.join("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = layouts.len();
|
||||||
|
layouts.push(l);
|
||||||
|
NestRef::Dynamic { id: LayoutId(id) }
|
||||||
}
|
}
|
||||||
} else if attr.path.is_ident("end_nest") {
|
};
|
||||||
current_base_route.pop();
|
nest_stack.push(nest_ref);
|
||||||
}
|
} else if attr.path.is_ident("end_nest") {
|
||||||
|
nest_stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
let route = Route::parse(current_base_route.join("/"), variant)?;
|
|
||||||
routes.push(route);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let myself = Self {
|
let mut trailing_static_route = nest_stack
|
||||||
route_name: name.clone(),
|
.iter()
|
||||||
routes,
|
.rev()
|
||||||
};
|
.map_while(|nest| match nest {
|
||||||
|
NestRef::Static(s) => Some(s.clone()),
|
||||||
|
NestRef::Dynamic { .. } => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
trailing_static_route.reverse();
|
||||||
|
let active_layouts = nest_stack
|
||||||
|
.iter()
|
||||||
|
.filter_map(|nest| match nest {
|
||||||
|
NestRef::Static(_) => None,
|
||||||
|
NestRef::Dynamic { id } => Some(*id),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(myself)
|
let route = Route::parse(trailing_static_route.join("/"), active_layouts, variant)?;
|
||||||
} else {
|
routes.push(route);
|
||||||
Err(syn::Error::new_spanned(
|
|
||||||
input.clone(),
|
|
||||||
"Routable can only be derived for enums",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let myself = Self {
|
||||||
|
vis: data.vis,
|
||||||
|
attrs: data.attrs,
|
||||||
|
name: name.clone(),
|
||||||
|
routes,
|
||||||
|
layouts,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(myself)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_display(&self) -> TokenStream2 {
|
fn impl_display(&self) -> TokenStream2 {
|
||||||
let mut display_match = Vec::new();
|
let mut display_match = Vec::new();
|
||||||
|
|
||||||
for route in &self.routes {
|
for route in &self.routes {
|
||||||
display_match.push(route.display_match());
|
display_match.push(route.display_match(&self.layouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = &self.route_name;
|
let name = &self.name;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl std::fmt::Display for #name {
|
impl std::fmt::Display for #name {
|
||||||
|
@ -111,13 +158,14 @@ impl RouteEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_impl(&self) -> TokenStream2 {
|
fn parse_impl(&self) -> TokenStream2 {
|
||||||
let tree = RouteTreeSegment::build(&self.routes);
|
let tree = RouteTree::new(&self.routes, &self.layouts);
|
||||||
let name = &self.route_name;
|
let name = &self.name;
|
||||||
|
|
||||||
let error_name = format_ident!("{}MatchError", self.route_name);
|
let error_name = format_ident!("{}MatchError", self.name);
|
||||||
let tokens = tree
|
let tokens = tree.roots.iter().map(|&id| {
|
||||||
.into_iter()
|
let route = tree.get(id).unwrap();
|
||||||
.map(|t| t.to_tokens(self.route_name.clone(), error_name.clone()));
|
route.to_tokens(&tree, self.name.clone(), error_name.clone(), &self.layouts)
|
||||||
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl<'a> TryFrom<&'a str> for #name {
|
impl<'a> TryFrom<&'a str> for #name {
|
||||||
|
@ -148,10 +196,7 @@ impl RouteEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_name(&self) -> Ident {
|
fn error_name(&self) -> Ident {
|
||||||
Ident::new(
|
Ident::new(&(self.name.to_string() + "MatchError"), Span::call_site())
|
||||||
&(self.route_name.to_string() + "MatchError"),
|
|
||||||
Span::call_site(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_type(&self) -> TokenStream2 {
|
fn error_type(&self) -> TokenStream2 {
|
||||||
|
@ -164,7 +209,7 @@ impl RouteEnum {
|
||||||
for route in &self.routes {
|
for route in &self.routes {
|
||||||
let route_name = &route.route_name;
|
let route_name = &route.route_name;
|
||||||
|
|
||||||
let error_name = Ident::new(&format!("{}ParseError", route_name), Span::call_site());
|
let error_name = route.error_ident();
|
||||||
let route_str = &route.route;
|
let route_str = &route.route;
|
||||||
|
|
||||||
error_variants.push(quote! { #route_name(#error_name) });
|
error_variants.push(quote! { #route_name(#error_name) });
|
||||||
|
@ -172,6 +217,17 @@ impl RouteEnum {
|
||||||
type_defs.push(route.error_type());
|
type_defs.push(route.error_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for layout in &self.layouts {
|
||||||
|
let layout_name = &layout.layout_name;
|
||||||
|
|
||||||
|
let error_name = layout.error_ident();
|
||||||
|
let route_str = &layout.route;
|
||||||
|
|
||||||
|
error_variants.push(quote! { #layout_name(#error_name) });
|
||||||
|
display_match.push(quote! { Self::#layout_name(err) => write!(f, "Layout '{}' ('{}') did not match:\n{}", stringify!(#layout_name), #route_str, err)? });
|
||||||
|
type_defs.push(layout.error_type());
|
||||||
|
}
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#(#type_defs)*
|
#(#type_defs)*
|
||||||
|
|
||||||
|
@ -192,17 +248,47 @@ impl RouteEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn routable_impl(&self) -> TokenStream2 {
|
fn routable_impl(&self) -> TokenStream2 {
|
||||||
let mut routable_match = Vec::new();
|
let name = &self.name;
|
||||||
|
|
||||||
for route in &self.routes {
|
let mut layers = Vec::new();
|
||||||
routable_match.push(route.routable_match());
|
|
||||||
|
loop {
|
||||||
|
let index = layers.len();
|
||||||
|
let mut routable_match = Vec::new();
|
||||||
|
|
||||||
|
// Collect all routes that match the current layer
|
||||||
|
for route in &self.routes {
|
||||||
|
if let Some(matched) = route.routable_match(&self.layouts, index) {
|
||||||
|
routable_match.push(matched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All routes are exhausted
|
||||||
|
if routable_match.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.push(quote! {
|
||||||
|
#(#routable_match)*
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let index_iter = 0..layers.len();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl Routable for Route {
|
impl Routable for #name where Self: Clone {
|
||||||
fn render<'a>(self, cx: &'a ScopeState) -> Element<'a> {
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
|
||||||
match self {
|
let myself = self.clone();
|
||||||
#(#routable_match)*
|
match level {
|
||||||
|
#(
|
||||||
|
#index_iter => {
|
||||||
|
match myself {
|
||||||
|
#layers
|
||||||
|
_ => panic!("Route::render called with invalid level {}", level),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
_ => panic!("Route::render called with invalid level {}", level),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,8 +299,17 @@ impl RouteEnum {
|
||||||
impl ToTokens for RouteEnum {
|
impl ToTokens for RouteEnum {
|
||||||
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
||||||
let routes = &self.routes;
|
let routes = &self.routes;
|
||||||
|
let vis = &self.vis;
|
||||||
|
let name = &self.name;
|
||||||
|
let attrs = &self.attrs;
|
||||||
|
let variants = routes.iter().map(|r| r.variant(&self.layouts));
|
||||||
|
|
||||||
tokens.extend(quote!(
|
tokens.extend(quote!(
|
||||||
|
#(#attrs)*
|
||||||
|
#vis enum #name {
|
||||||
|
#(#variants),*
|
||||||
|
}
|
||||||
|
|
||||||
#[path = "pages"]
|
#[path = "pages"]
|
||||||
mod pages {
|
mod pages {
|
||||||
#(#routes)*
|
#(#routes)*
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use quote::format_ident;
|
use proc_macro2::TokenStream;
|
||||||
use syn::{parse::Parse, Ident, LitStr, Variant};
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{parse::Parse, Ident, LitStr};
|
||||||
|
|
||||||
use crate::segment::RouteSegment;
|
use crate::segment::{parse_route_segments, RouteSegment};
|
||||||
|
|
||||||
pub enum Nest {
|
pub enum Nest {
|
||||||
Static(String),
|
Static(String),
|
||||||
|
@ -12,28 +13,45 @@ impl Parse for Nest {
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
// First parse the route
|
// First parse the route
|
||||||
let route: LitStr = input.parse()?;
|
let route: LitStr = input.parse()?;
|
||||||
|
let is_dynamic = route.value().contains('(');
|
||||||
|
|
||||||
if route.value().contains('(') {
|
if !input.is_empty() || is_dynamic {
|
||||||
// Then parse the layout name
|
// Then parse the layout name
|
||||||
let _ = input.parse::<syn::Token![,]>();
|
let _ = input.parse::<syn::Token![,]>();
|
||||||
let layout_name: Ident = input.parse()?;
|
let layout_name: syn::Ident = input.parse()?;
|
||||||
|
let layout_fields: syn::FieldsNamed = input.parse()?;
|
||||||
|
|
||||||
// Then parse the component name
|
// Then parse the component name
|
||||||
let _ = input.parse::<syn::Token![,]>();
|
let _ = input.parse::<syn::Token![,]>();
|
||||||
let comp: Variant = input.parse()?;
|
let comp: Ident = input.parse()?;
|
||||||
|
|
||||||
// Then parse the props name
|
// Then parse the props name
|
||||||
let _ = input.parse::<syn::Token![,]>();
|
let _ = input.parse::<syn::Token![,]>();
|
||||||
let props_name: Ident = input
|
let props_name: Ident = input
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or_else(|_| format_ident!("{}Props", comp.ident.to_string()));
|
.unwrap_or_else(|_| format_ident!("{}Props", comp.to_string()));
|
||||||
|
|
||||||
|
let route_segments =
|
||||||
|
parse_route_segments(&layout_name, &layout_fields, &route.value())?.0;
|
||||||
|
for seg in &route_segments {
|
||||||
|
if let RouteSegment::CatchAll(name, _) = seg {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
name,
|
||||||
|
format!(
|
||||||
|
"Catch-all segments are not allowed in nested routes: {}",
|
||||||
|
route.value()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self::Layout(Layout {
|
Ok(Self::Layout(Layout {
|
||||||
route: route.value(),
|
route: route.value(),
|
||||||
route_segments: Vec::new(),
|
segments: route_segments,
|
||||||
layout_name,
|
layout_name,
|
||||||
comp,
|
comp,
|
||||||
props_name,
|
props_name,
|
||||||
|
layout_fields,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Ok(Self::Static(route.value()))
|
Ok(Self::Static(route.value()))
|
||||||
|
@ -41,40 +59,111 @@ impl Parse for Nest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Layout {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LayoutId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Layout {
|
||||||
pub route: String,
|
pub route: String,
|
||||||
pub route_segments: Vec<RouteSegment>,
|
pub segments: Vec<RouteSegment>,
|
||||||
pub layout_name: Ident,
|
pub layout_name: Ident,
|
||||||
pub comp: Variant,
|
pub layout_fields: syn::FieldsNamed,
|
||||||
|
pub comp: Ident,
|
||||||
pub props_name: Ident,
|
pub props_name: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Clone, Debug, PartialEq, Routable)]
|
impl Layout {
|
||||||
// enum Route {
|
pub fn add_static_prefix(&mut self, prefix: &str) {
|
||||||
// // Each Variant is a route with a linked component, dynamic segments are defined with the syntax: (name) and the type is inferred from the field type. The type must implement FromStr
|
self.route = format!("{}{}", prefix, self.route);
|
||||||
// #[route("/(dynamic)" Component1)]
|
self.segments.push(RouteSegment::Static(prefix.to_string()));
|
||||||
// Route1 { dynamic: usize },
|
}
|
||||||
// // You can nest routes which makes all routes in the block relative to a parent route. Nested routes are flattened into the parent enum
|
|
||||||
// // Nest accepts a optional layout component. The layout component that wraps all children and renders them where the Outlet component is found. It can accept parameters from the nested route, just like a normal route
|
pub fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||||
// #[nest("/(dynamic)" root_dynamic_segment Component { dynamic: String })]
|
self.segments
|
||||||
// // If the component is not specified, the component is assumed to be at the path of the route (in this case /pages/hello_world.rs or /pages/hello_world/index.rs)
|
.iter()
|
||||||
// #[route("/")]
|
.filter_map(|seg| seg.name())
|
||||||
// // You can opt out of a parent Layout
|
.map(|i| quote! {#i})
|
||||||
// #[layout(!root_dynamic_segment)]
|
}
|
||||||
// Route2 {
|
|
||||||
// // implicitly adds
|
pub fn dynamic_segment_types(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||||
// // root_dynamic_segment: ComponentProps,
|
self.segments
|
||||||
// },
|
.iter()
|
||||||
// #[end_nest]
|
.filter_map(|seg| seg.ty())
|
||||||
// // Queries are defined with the syntax: ?(name) and the type is inferred from the field type. The type must implement From<&str> (not FromStr because the query parsing must be infallible). The query part of the url is not included in the route path for file based routing. (in this case /pages/takes_query.rs or /pages/takes_query/index.rs)
|
.map(|ty| quote! {#ty})
|
||||||
// #[route("/takes_query?(dynamic)")]
|
}
|
||||||
// Route3 { dynamic: u32 },
|
|
||||||
// // Redirects are defined with the redirect attribute
|
pub fn write(&self) -> TokenStream {
|
||||||
// #[redirect("/old_hello_world/(dynamic)")]
|
let write_segments = self.segments.iter().map(|s| s.write_segment());
|
||||||
// #[route("/hello_world/(dynamic)")]
|
|
||||||
// Route4 { dynamic: u32 },
|
quote! {
|
||||||
// // members that can be parsed from all trailing segments are defined with the syntax: (...name) and the type is inferred from the field type. The type must implement FromSegments.
|
{
|
||||||
// // Because this route is defined after Route3, it will only be matched if Route3 does not match and it will act as a fallback
|
#(#write_segments)*
|
||||||
// #[route("/(...number2)")]
|
}
|
||||||
// Route5 { number1: u32, number2: u32 },
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
pub fn error_ident(&self) -> Ident {
|
||||||
|
format_ident!("{}LayoutParseError", self.layout_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_type(&self) -> TokenStream {
|
||||||
|
let error_name = self.error_ident();
|
||||||
|
|
||||||
|
let mut error_variants = Vec::new();
|
||||||
|
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_core::router::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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routable_match(&self) -> TokenStream {
|
||||||
|
let props_name = &self.props_name;
|
||||||
|
let comp_name = &self.comp;
|
||||||
|
let dynamic_segments_from_route = self
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.filter_map(|seg| seg.name())
|
||||||
|
.map(|seg| quote! { #seg });
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
let comp = #props_name { #(#dynamic_segments_from_route,)* };
|
||||||
|
let cx = cx.bump().alloc(Scoped {
|
||||||
|
props: cx.bump().alloc(comp),
|
||||||
|
scope: cx,
|
||||||
|
});
|
||||||
|
#comp_name(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,4 +28,8 @@ impl QuerySegment {
|
||||||
pub fn name(&self) -> Ident {
|
pub fn name(&self) -> Ident {
|
||||||
self.ident.clone()
|
self.ident.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self) -> &Type {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ use syn::{Ident, LitStr};
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
|
||||||
|
use crate::nest::Layout;
|
||||||
|
use crate::nest::LayoutId;
|
||||||
use crate::query::QuerySegment;
|
use crate::query::QuerySegment;
|
||||||
use crate::segment::parse_route_segments;
|
use crate::segment::parse_route_segments;
|
||||||
use crate::segment::RouteSegment;
|
use crate::segment::RouteSegment;
|
||||||
|
@ -40,24 +42,30 @@ pub struct Route {
|
||||||
pub comp_name: Ident,
|
pub comp_name: Ident,
|
||||||
pub props_name: Ident,
|
pub props_name: Ident,
|
||||||
pub route: String,
|
pub route: String,
|
||||||
pub route_segments: Vec<RouteSegment>,
|
pub segments: Vec<RouteSegment>,
|
||||||
pub query: Option<QuerySegment>,
|
pub query: Option<QuerySegment>,
|
||||||
|
pub layouts: Vec<LayoutId>,
|
||||||
|
pub variant: syn::Variant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Route {
|
impl Route {
|
||||||
pub fn parse(root_route: String, input: syn::Variant) -> syn::Result<Self> {
|
pub fn parse(
|
||||||
let route_attr = input
|
root_route: String,
|
||||||
|
layouts: Vec<LayoutId>,
|
||||||
|
variant: syn::Variant,
|
||||||
|
) -> syn::Result<Self> {
|
||||||
|
let route_attr = variant
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|attr| attr.path.is_ident("route"))
|
.find(|attr| attr.path.is_ident("route"))
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
syn::Error::new_spanned(
|
syn::Error::new_spanned(
|
||||||
input.clone(),
|
variant.clone(),
|
||||||
"Routable variants must have a #[route(...)] attribute",
|
"Routable variants must have a #[route(...)] attribute",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let route_name = input.ident.clone();
|
let route_name = variant.ident.clone();
|
||||||
let args = route_attr.parse_args::<RouteArgs>()?;
|
let args = route_attr.parse_args::<RouteArgs>()?;
|
||||||
let route = root_route + &args.route.value();
|
let route = root_route + &args.route.value();
|
||||||
let file_based = args.comp_name.is_none();
|
let file_based = args.comp_name.is_none();
|
||||||
|
@ -68,53 +76,115 @@ impl Route {
|
||||||
.props_name
|
.props_name
|
||||||
.unwrap_or_else(|| format_ident!("{}Props", comp_name));
|
.unwrap_or_else(|| format_ident!("{}Props", comp_name));
|
||||||
|
|
||||||
let (route_segments, query) = parse_route_segments(&input, &route)?;
|
let named_fields = match &variant.fields {
|
||||||
|
syn::Fields::Named(fields) => fields,
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
variant.clone(),
|
||||||
|
"Routable variants must have named fields",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (route_segments, query) = parse_route_segments(&variant.ident, named_fields, &route)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
comp_name,
|
comp_name,
|
||||||
props_name,
|
props_name,
|
||||||
route_name,
|
route_name,
|
||||||
route_segments,
|
segments: route_segments,
|
||||||
route,
|
route,
|
||||||
file_based,
|
file_based,
|
||||||
query,
|
query,
|
||||||
|
layouts,
|
||||||
|
variant,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_match(&self) -> TokenStream2 {
|
pub fn display_match(&self, layouts: &[Layout]) -> TokenStream2 {
|
||||||
let name = &self.route_name;
|
let name = &self.route_name;
|
||||||
let dynamic_segments = self.dynamic_segments();
|
let dynamic_segments = self.dynamic_segments(layouts);
|
||||||
let write_segments = self.route_segments.iter().map(|s| s.write_segment());
|
let write_layouts = self.layouts.iter().map(|id| layouts[id.0].write());
|
||||||
|
let write_segments = self.segments.iter().map(|s| s.write_segment());
|
||||||
let write_query = self.query.as_ref().map(|q| q.write());
|
let write_query = self.query.as_ref().map(|q| q.write());
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
Self::#name { #(#dynamic_segments,)* } => {
|
Self::#name { #(#dynamic_segments,)* } => {
|
||||||
|
#(#write_layouts)*
|
||||||
#(#write_segments)*
|
#(#write_segments)*
|
||||||
#write_query
|
#write_query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn routable_match(&self) -> TokenStream2 {
|
pub fn routable_match(&self, layouts: &[Layout], index: usize) -> Option<TokenStream2> {
|
||||||
let name = &self.route_name;
|
let name = &self.route_name;
|
||||||
let dynamic_segments: Vec<_> = self.dynamic_segments().collect();
|
let dynamic_segments = self.dynamic_segments(layouts);
|
||||||
let props_name = &self.props_name;
|
|
||||||
let comp_name = &self.comp_name;
|
|
||||||
|
|
||||||
quote! {
|
match index.cmp(&self.layouts.len()) {
|
||||||
Self::#name { #(#dynamic_segments,)* } => {
|
std::cmp::Ordering::Less => {
|
||||||
let comp = #props_name { #(#dynamic_segments,)* };
|
let layout = self.layouts[index];
|
||||||
let cx = cx.bump().alloc(Scoped {
|
let render_layout = layouts[layout.0].routable_match();
|
||||||
props: cx.bump().alloc(comp),
|
// This is a layout
|
||||||
scope: cx,
|
Some(quote! {
|
||||||
});
|
#[allow(unused)]
|
||||||
#comp_name(cx)
|
Self::#name { #(#dynamic_segments,)* } => {
|
||||||
|
#render_layout
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
std::cmp::Ordering::Equal => {
|
||||||
|
let dynamic_segments_from_route = self.dynamic_segments_from_route();
|
||||||
|
let props_name = &self.props_name;
|
||||||
|
let comp_name = &self.comp_name;
|
||||||
|
// This is the final route
|
||||||
|
Some(quote! {
|
||||||
|
#[allow(unused)]
|
||||||
|
Self::#name { #(#dynamic_segments,)* } => {
|
||||||
|
let comp = #props_name { #(#dynamic_segments_from_route,)* };
|
||||||
|
let cx = cx.bump().alloc(Scoped {
|
||||||
|
props: cx.bump().alloc(comp),
|
||||||
|
scope: cx,
|
||||||
|
});
|
||||||
|
#comp_name(cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
|
fn dynamic_segment_types<'a>(
|
||||||
let segments = self.route_segments.iter().filter_map(|seg| {
|
&'a self,
|
||||||
|
layouts: &'a [Layout],
|
||||||
|
) -> impl Iterator<Item = TokenStream2> + 'a {
|
||||||
|
let layouts = self
|
||||||
|
.layouts
|
||||||
|
.iter()
|
||||||
|
.flat_map(|id| layouts[id.0].dynamic_segment_types());
|
||||||
|
let segments = self.segments.iter().filter_map(|seg| {
|
||||||
|
let ty = seg.ty()?;
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
#ty
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let query = self
|
||||||
|
.query
|
||||||
|
.as_ref()
|
||||||
|
.map(|q| {
|
||||||
|
let ty = q.ty();
|
||||||
|
quote! {
|
||||||
|
#ty
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
layouts.chain(segments.chain(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dynamic_segments_from_route(&self) -> impl Iterator<Item = TokenStream2> + '_ {
|
||||||
|
let segments = self.segments.iter().filter_map(|seg| {
|
||||||
seg.name().map(|name| {
|
seg.name().map(|name| {
|
||||||
quote! {
|
quote! {
|
||||||
#name
|
#name
|
||||||
|
@ -135,8 +205,21 @@ impl Route {
|
||||||
segments.chain(query)
|
segments.chain(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
|
fn dynamic_segments<'a>(
|
||||||
let segments = self.dynamic_segments();
|
&'a self,
|
||||||
|
layouts: &'a [Layout],
|
||||||
|
) -> impl Iterator<Item = TokenStream2> + 'a {
|
||||||
|
let layouts = self
|
||||||
|
.layouts
|
||||||
|
.iter()
|
||||||
|
.flat_map(|id| layouts[id.0].dynamic_segments());
|
||||||
|
let dynamic_segments = self.dynamic_segments_from_route();
|
||||||
|
|
||||||
|
layouts.chain(dynamic_segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct(&self, enum_name: Ident, layouts: &[Layout]) -> TokenStream2 {
|
||||||
|
let segments = self.dynamic_segments(layouts);
|
||||||
let name = &self.route_name;
|
let name = &self.route_name;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -156,7 +239,7 @@ impl Route {
|
||||||
let mut error_variants = Vec::new();
|
let mut error_variants = Vec::new();
|
||||||
let mut display_match = Vec::new();
|
let mut display_match = Vec::new();
|
||||||
|
|
||||||
for (i, segment) in self.route_segments.iter().enumerate() {
|
for (i, segment) in self.segments.iter().enumerate() {
|
||||||
let error_name = segment.error_name(i);
|
let error_name = segment.error_name(i);
|
||||||
match segment {
|
match segment {
|
||||||
RouteSegment::Static(index) => {
|
RouteSegment::Static(index) => {
|
||||||
|
@ -205,6 +288,16 @@ impl Route {
|
||||||
None => quote! {},
|
None => quote! {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn variant(&self, layouts: &[Layout]) -> TokenStream2 {
|
||||||
|
let name = &self.route_name;
|
||||||
|
let segments = self.dynamic_segments(layouts);
|
||||||
|
let types = self.dynamic_segment_types(layouts);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#name { #(#segments: #types,)* }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Route {
|
impl ToTokens for Route {
|
||||||
|
|
|
@ -1,94 +1,283 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use slab::Slab;
|
||||||
use syn::Ident;
|
use syn::Ident;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
nest::Layout,
|
||||||
route::Route,
|
route::Route,
|
||||||
segment::{static_segment_idx, RouteSegment},
|
segment::{static_segment_idx, RouteSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
// First deduplicate the routes by the static part of the route
|
#[derive(Debug, Clone, Default)]
|
||||||
#[derive(Debug)]
|
pub struct RouteTree<'a> {
|
||||||
pub enum RouteTreeSegment<'a> {
|
pub roots: Vec<usize>,
|
||||||
Static {
|
entries: Slab<RouteTreeSegmentData<'a>>,
|
||||||
index: usize,
|
|
||||||
segment: &'a str,
|
|
||||||
children: Vec<RouteTreeSegment<'a>>,
|
|
||||||
from_route: &'a Route,
|
|
||||||
},
|
|
||||||
Dynamic(&'a Route),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RouteTreeSegment<'a> {
|
impl<'a> RouteTree<'a> {
|
||||||
pub fn build(routes: &'a [Route]) -> Vec<RouteTreeSegment<'a>> {
|
pub fn get(&self, index: usize) -> Option<&RouteTreeSegmentData<'a>> {
|
||||||
let routes = routes.iter().map(PartialRoute::new).collect();
|
self.entries.get(index)
|
||||||
Self::construct(routes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct(routes: Vec<PartialRoute<'a>>) -> Vec<RouteTreeSegment<'a>> {
|
pub fn get_mut(&mut self, element: usize) -> Option<&mut RouteTreeSegmentData<'a>> {
|
||||||
let mut static_segments = Vec::new();
|
self.entries.get_mut(element)
|
||||||
let mut dyn_segments = Vec::new();
|
}
|
||||||
|
|
||||||
// Add all routes we can to the tree
|
fn sort_children(&mut self) {
|
||||||
|
let mut old_roots = self.roots.clone();
|
||||||
|
self.sort_ids(&mut old_roots);
|
||||||
|
self.roots = old_roots;
|
||||||
|
|
||||||
|
for id in self.roots.clone() {
|
||||||
|
self.sort_children_of_id(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_ids(&self, ids: &mut [usize]) {
|
||||||
|
ids.sort_by_key(|&seg| {
|
||||||
|
let seg = self.get(seg).unwrap();
|
||||||
|
match seg {
|
||||||
|
RouteTreeSegmentData::Static { .. } => 0,
|
||||||
|
RouteTreeSegmentData::Layout { .. } => 1,
|
||||||
|
RouteTreeSegmentData::Route(_) => 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_children_of_id(&mut self, id: usize) {
|
||||||
|
// Sort segments so that all static routes are checked before dynamic routes
|
||||||
|
let mut children = self.children(id);
|
||||||
|
|
||||||
|
self.sort_ids(&mut children);
|
||||||
|
|
||||||
|
if let Some(old) = self.try_children_mut(id) {
|
||||||
|
old.clone_from(&children)
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in children {
|
||||||
|
self.sort_children_of_id(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self, element: usize) -> Vec<usize> {
|
||||||
|
let element = self.entries.get(element).unwrap();
|
||||||
|
match element {
|
||||||
|
RouteTreeSegmentData::Static { children, .. } => children.clone(),
|
||||||
|
RouteTreeSegmentData::Layout { children, .. } => children.clone(),
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_children_mut(&mut self, element: usize) -> Option<&mut Vec<usize>> {
|
||||||
|
let element = self.entries.get_mut(element).unwrap();
|
||||||
|
match element {
|
||||||
|
RouteTreeSegmentData::Static { children, .. } => Some(children),
|
||||||
|
RouteTreeSegmentData::Layout { children, .. } => Some(children),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children_mut(&mut self, element: usize) -> &mut Vec<usize> {
|
||||||
|
self.try_children_mut(element)
|
||||||
|
.expect("Cannot get children of non static or layout segment")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(routes: &'a [Route], layouts: &'a [Layout]) -> Self {
|
||||||
|
let routes = routes
|
||||||
|
.iter()
|
||||||
|
.map(|route| RouteIter::new(route, layouts))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut myself = Self::default();
|
||||||
|
myself.roots = myself.construct(routes);
|
||||||
|
myself.sort_children();
|
||||||
|
|
||||||
|
myself
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct(&mut self, routes: Vec<RouteIter<'a>>) -> Vec<usize> {
|
||||||
|
let mut segments = Vec::new();
|
||||||
|
|
||||||
|
// Add all routes to the tree
|
||||||
for mut route in routes {
|
for mut route in routes {
|
||||||
match route.next_static_segment() {
|
let mut current_route: Option<usize> = None;
|
||||||
// If there is a static segment, check if it already exists in the tree
|
|
||||||
Some((i, segment)) => {
|
|
||||||
let found = static_segments.iter_mut().find_map(|seg| match seg {
|
|
||||||
RouteTreeSegment::Static {
|
|
||||||
segment: s,
|
|
||||||
children,
|
|
||||||
..
|
|
||||||
} => (s == &segment).then_some(children),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
match found {
|
// First add a layout if there is one
|
||||||
Some(children) => {
|
while let Some(layout) = route.next_layout() {
|
||||||
// If it does, add the route to the children of the segment
|
let segments_iter: std::slice::Iter<RouteSegment> = layout.segments.iter();
|
||||||
children.append(&mut RouteTreeSegment::construct(vec![route]))
|
|
||||||
}
|
// Add all static segments of the layout
|
||||||
None => {
|
'o: for (index, segment) in segments_iter.enumerate() {
|
||||||
// If it doesn't, add the route as a new segment
|
match segment {
|
||||||
static_segments.push(RouteTreeSegment::Static {
|
RouteSegment::Static(segment) => {
|
||||||
|
// Check if the segment already exists
|
||||||
|
{
|
||||||
|
// Either look for the segment in the current route or in the static segments
|
||||||
|
let segments = current_route
|
||||||
|
.map(|id| self.children(id))
|
||||||
|
.unwrap_or_else(|| segments.clone());
|
||||||
|
|
||||||
|
for seg in segments.iter() {
|
||||||
|
let seg = self.get(*seg).unwrap();
|
||||||
|
if let RouteTreeSegmentData::Static {
|
||||||
|
segment: s,
|
||||||
|
children,
|
||||||
|
..
|
||||||
|
} = seg
|
||||||
|
{
|
||||||
|
if s == segment {
|
||||||
|
// If it does, just update the current route
|
||||||
|
current_route = children.last().cloned();
|
||||||
|
continue 'o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let static_segment = RouteTreeSegmentData::Static {
|
||||||
segment,
|
segment,
|
||||||
from_route: route.route,
|
children: Vec::new(),
|
||||||
children: RouteTreeSegment::construct(vec![route]),
|
error_variant: route.error_variant(),
|
||||||
index: i,
|
index,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// If it doesn't, add the segment to the current route
|
||||||
|
let static_segment = self.entries.insert(static_segment);
|
||||||
|
|
||||||
|
let current_children = current_route
|
||||||
|
.map(|id| self.children_mut(id))
|
||||||
|
.unwrap_or_else(|| &mut segments);
|
||||||
|
current_children.push(static_segment);
|
||||||
|
}
|
||||||
|
// If there is a dynamic segment, stop adding static segments
|
||||||
|
RouteSegment::Dynamic(..) => break,
|
||||||
|
RouteSegment::CatchAll(..) => {
|
||||||
|
todo!("Catch all segments are not allowed in layouts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there is no static segment, add the route to the dynamic routes
|
|
||||||
|
// Add the layout to the current route
|
||||||
|
let layout = RouteTreeSegmentData::Layout {
|
||||||
|
layout,
|
||||||
|
children: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layout = self.entries.insert(layout);
|
||||||
|
let segments = match current_route.and_then(|id| self.get_mut(id)) {
|
||||||
|
Some(RouteTreeSegmentData::Static { children, .. }) => children,
|
||||||
|
Some(_) => unreachable!(),
|
||||||
|
None => &mut segments,
|
||||||
|
};
|
||||||
|
segments.push(layout);
|
||||||
|
|
||||||
|
// Update the current route
|
||||||
|
current_route = segments.last().cloned();
|
||||||
|
}
|
||||||
|
|
||||||
|
match route.next_static_segment() {
|
||||||
|
// If there is a static segment, check if it already exists in the tree
|
||||||
|
Some((i, segment)) => {
|
||||||
|
let current_children = current_route
|
||||||
|
.map(|id| self.children(id))
|
||||||
|
.unwrap_or_else(|| segments.clone());
|
||||||
|
let found = current_children.iter().find_map(|&id| {
|
||||||
|
let seg = self.get(id).unwrap();
|
||||||
|
match seg {
|
||||||
|
RouteTreeSegmentData::Static { segment: s, .. } => {
|
||||||
|
(s == &segment).then_some(id)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match found {
|
||||||
|
Some(id) => {
|
||||||
|
// If it exists, add the route to the children of the segment
|
||||||
|
let new_children = self.construct(vec![route]);
|
||||||
|
self.children_mut(id).extend(new_children.into_iter());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// If it doesn't exist, add the route as a new segment
|
||||||
|
let data = RouteTreeSegmentData::Static {
|
||||||
|
segment,
|
||||||
|
error_variant: route.error_variant(),
|
||||||
|
children: self.construct(vec![route]),
|
||||||
|
index: i,
|
||||||
|
};
|
||||||
|
let id = self.entries.insert(data);
|
||||||
|
let current_children_mut = current_route
|
||||||
|
.map(|id| self.children_mut(id))
|
||||||
|
.unwrap_or_else(|| &mut segments);
|
||||||
|
current_children_mut.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there is no static segment, add the route to the current_route
|
||||||
None => {
|
None => {
|
||||||
dyn_segments.push(RouteTreeSegment::Dynamic(route.route));
|
let id = self
|
||||||
|
.entries
|
||||||
|
.insert(RouteTreeSegmentData::Route(route.route));
|
||||||
|
let current_children_mut = current_route
|
||||||
|
.map(|id| self.children_mut(id))
|
||||||
|
.unwrap_or_else(|| &mut segments);
|
||||||
|
current_children_mut.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All static routes are checked before dynamic routes
|
segments
|
||||||
static_segments.append(&mut dyn_segments);
|
|
||||||
|
|
||||||
static_segments
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RouteTreeSegment<'a> {
|
#[derive(Debug, Clone)]
|
||||||
pub fn to_tokens(&self, enum_name: syn::Ident, error_enum_name: syn::Ident) -> TokenStream {
|
pub struct StaticErrorVariant {
|
||||||
|
varient_parse_error: Ident,
|
||||||
|
enum_varient: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
// First deduplicate the routes by the static part of the route
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RouteTreeSegmentData<'a> {
|
||||||
|
Static {
|
||||||
|
segment: &'a str,
|
||||||
|
error_variant: StaticErrorVariant,
|
||||||
|
index: usize,
|
||||||
|
children: Vec<usize>,
|
||||||
|
},
|
||||||
|
Layout {
|
||||||
|
layout: &'a Layout,
|
||||||
|
children: Vec<usize>,
|
||||||
|
},
|
||||||
|
Route(&'a Route),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RouteTreeSegmentData<'a> {
|
||||||
|
pub fn to_tokens(
|
||||||
|
&self,
|
||||||
|
tree: &RouteTree,
|
||||||
|
enum_name: syn::Ident,
|
||||||
|
error_enum_name: syn::Ident,
|
||||||
|
layouts: &[Layout],
|
||||||
|
) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
RouteTreeSegment::Static {
|
RouteTreeSegmentData::Static {
|
||||||
segment,
|
segment,
|
||||||
children,
|
children,
|
||||||
index,
|
index,
|
||||||
from_route,
|
error_variant:
|
||||||
|
StaticErrorVariant {
|
||||||
|
varient_parse_error,
|
||||||
|
enum_varient,
|
||||||
|
},
|
||||||
} => {
|
} => {
|
||||||
let varient_parse_error = from_route.error_ident();
|
|
||||||
let enum_varient = &from_route.route_name;
|
|
||||||
let error_ident = static_segment_idx(*index);
|
let error_ident = static_segment_idx(*index);
|
||||||
|
|
||||||
let children = children
|
let children = children.iter().map(|child| {
|
||||||
.iter()
|
let child = tree.get(*child).unwrap();
|
||||||
.map(|child| child.to_tokens(enum_name.clone(), error_enum_name.clone()));
|
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
|
||||||
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
|
@ -104,52 +293,22 @@ impl<'a> RouteTreeSegment<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RouteTreeSegment::Dynamic(route) => {
|
RouteTreeSegmentData::Route(route) => {
|
||||||
// At this point, we have matched all static segments, so we can just check if the remaining segments match the route
|
// 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 = route.error_ident();
|
let varient_parse_error = route.error_ident();
|
||||||
let enum_varient = &route.route_name;
|
let enum_varient = &route.route_name;
|
||||||
|
|
||||||
let route_segments = route
|
let route_segments = route
|
||||||
.route_segments
|
.segments
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
|
.skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
|
||||||
|
|
||||||
fn print_route_segment<'a, I: Iterator<Item = (usize, &'a RouteSegment)>>(
|
let construct_variant = route.construct(enum_name, layouts);
|
||||||
mut s: std::iter::Peekable<I>,
|
|
||||||
sucess_tokens: TokenStream,
|
|
||||||
error_enum_name: &Ident,
|
|
||||||
enum_varient: &Ident,
|
|
||||||
varient_parse_error: &Ident,
|
|
||||||
) -> TokenStream {
|
|
||||||
if let Some((i, route)) = s.next() {
|
|
||||||
let children = print_route_segment(
|
|
||||||
s,
|
|
||||||
sucess_tokens,
|
|
||||||
error_enum_name,
|
|
||||||
enum_varient,
|
|
||||||
varient_parse_error,
|
|
||||||
);
|
|
||||||
|
|
||||||
route.try_parse(
|
|
||||||
i,
|
|
||||||
error_enum_name,
|
|
||||||
enum_varient,
|
|
||||||
varient_parse_error,
|
|
||||||
children,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
#sucess_tokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let construct_variant = route.construct(enum_name);
|
|
||||||
let parse_query = route.parse_query();
|
let parse_query = route.parse_query();
|
||||||
|
|
||||||
let insure_not_trailing = route
|
let insure_not_trailing = route
|
||||||
.route_segments
|
.segments
|
||||||
.last()
|
.last()
|
||||||
.map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
|
.map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
@ -169,6 +328,63 @@ impl<'a> RouteTreeSegment<'a> {
|
||||||
&varient_parse_error,
|
&varient_parse_error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Self::Layout { layout, children } => {
|
||||||
|
// 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: Ident = layout.error_ident();
|
||||||
|
let enum_varient = &layout.layout_name;
|
||||||
|
|
||||||
|
let route_segments = layout
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
|
||||||
|
|
||||||
|
let parse_children = children
|
||||||
|
.iter()
|
||||||
|
.map(|child| {
|
||||||
|
let child = tree.get(*child).unwrap();
|
||||||
|
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
print_route_segment(
|
||||||
|
route_segments.peekable(),
|
||||||
|
parse_children,
|
||||||
|
&error_enum_name,
|
||||||
|
enum_varient,
|
||||||
|
&varient_parse_error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_route_segment<'a, I: Iterator<Item = (usize, &'a RouteSegment)>>(
|
||||||
|
mut s: std::iter::Peekable<I>,
|
||||||
|
sucess_tokens: TokenStream,
|
||||||
|
error_enum_name: &Ident,
|
||||||
|
enum_varient: &Ident,
|
||||||
|
varient_parse_error: &Ident,
|
||||||
|
) -> TokenStream {
|
||||||
|
if let Some((i, route)) = s.next() {
|
||||||
|
let children = print_route_segment(
|
||||||
|
s,
|
||||||
|
sucess_tokens,
|
||||||
|
error_enum_name,
|
||||||
|
enum_varient,
|
||||||
|
varient_parse_error,
|
||||||
|
);
|
||||||
|
|
||||||
|
route.try_parse(
|
||||||
|
i,
|
||||||
|
error_enum_name,
|
||||||
|
enum_varient,
|
||||||
|
varient_parse_error,
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#sucess_tokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,22 +428,34 @@ fn return_constructed(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PartialRoute<'a> {
|
pub struct RouteIter<'a> {
|
||||||
route: &'a Route,
|
route: &'a Route,
|
||||||
|
layouts: &'a [Layout],
|
||||||
|
layout_index: usize,
|
||||||
static_segment_index: usize,
|
static_segment_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PartialRoute<'a> {
|
impl<'a> RouteIter<'a> {
|
||||||
fn new(route: &'a Route) -> Self {
|
fn new(route: &'a Route, layouts: &'a [Layout]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
route,
|
route,
|
||||||
|
layouts,
|
||||||
|
layout_index: 0,
|
||||||
static_segment_index: 0,
|
static_segment_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_layout(&mut self) -> Option<&'a Layout> {
|
||||||
|
let idx = self.layout_index;
|
||||||
|
let layout_index = self.route.layouts.get(idx)?;
|
||||||
|
let layout = &self.layouts[layout_index.0];
|
||||||
|
self.layout_index += 1;
|
||||||
|
Some(layout)
|
||||||
|
}
|
||||||
|
|
||||||
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.route_segments.get(idx)?;
|
let segment = self.route.segments.get(idx)?;
|
||||||
match segment {
|
match segment {
|
||||||
RouteSegment::Static(segment) => {
|
RouteSegment::Static(segment) => {
|
||||||
self.static_segment_index += 1;
|
self.static_segment_index += 1;
|
||||||
|
@ -236,4 +464,11 @@ impl<'a> PartialRoute<'a> {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error_variant(&self) -> StaticErrorVariant {
|
||||||
|
StaticErrorVariant {
|
||||||
|
varient_parse_error: self.route.error_ident(),
|
||||||
|
enum_varient: self.route.route_name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{Ident, Type, Variant};
|
use syn::{Ident, Type};
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
|
|
||||||
|
@ -21,6 +21,14 @@ impl RouteSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ty(&self) -> Option<&Type> {
|
||||||
|
match self {
|
||||||
|
Self::Static(_) => None,
|
||||||
|
Self::Dynamic(_, ty) => Some(ty),
|
||||||
|
Self::CatchAll(_, ty) => Some(ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_segment(&self) -> TokenStream2 {
|
pub fn write_segment(&self) -> TokenStream2 {
|
||||||
match self {
|
match self {
|
||||||
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
||||||
|
@ -123,7 +131,8 @@ pub fn static_segment_idx(idx: usize) -> Ident {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_route_segments(
|
pub fn parse_route_segments(
|
||||||
varient: &Variant,
|
route_name: &Ident,
|
||||||
|
fields: &syn::FieldsNamed,
|
||||||
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();
|
||||||
|
@ -138,7 +147,7 @@ pub fn parse_route_segments(
|
||||||
let first = iterator.next();
|
let first = iterator.next();
|
||||||
if first != Some("") {
|
if first != Some("") {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
varient,
|
route_name,
|
||||||
format!(
|
format!(
|
||||||
"Routes should start with /. Error found in the route '{}'",
|
"Routes should start with /. Error found in the route '{}'",
|
||||||
route
|
route
|
||||||
|
@ -156,7 +165,7 @@ pub fn parse_route_segments(
|
||||||
segment[1..segment.len() - 1].to_string()
|
segment[1..segment.len() - 1].to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let field = varient.fields.iter().find(|field| match field.ident {
|
let field = fields.named.iter().find(|field| match field.ident {
|
||||||
Some(ref field_ident) => *field_ident == ident,
|
Some(ref field_ident) => *field_ident == ident,
|
||||||
None => false,
|
None => false,
|
||||||
});
|
});
|
||||||
|
@ -165,10 +174,10 @@ pub fn parse_route_segments(
|
||||||
field.ty.clone()
|
field.ty.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
varient,
|
route_name,
|
||||||
format!(
|
format!(
|
||||||
"Could not find a field with the name '{}' in the variant '{}'",
|
"Could not find a field with the name '{}' in the variant '{}'",
|
||||||
ident, varient.ident
|
ident, route_name
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
@ -202,7 +211,7 @@ pub fn parse_route_segments(
|
||||||
Some(query) => {
|
Some(query) => {
|
||||||
if query.starts_with('(') && query.ends_with(')') {
|
if query.starts_with('(') && query.ends_with(')') {
|
||||||
let query_ident = Ident::new(&query[1..query.len() - 1], Span::call_site());
|
let query_ident = Ident::new(&query[1..query.len() - 1], Span::call_site());
|
||||||
let field = varient.fields.iter().find(|field| match field.ident {
|
let field = fields.named.iter().find(|field| match field.ident {
|
||||||
Some(ref field_ident) => field_ident == &query_ident,
|
Some(ref field_ident) => field_ident == &query_ident,
|
||||||
None => false,
|
None => false,
|
||||||
});
|
});
|
||||||
|
@ -211,10 +220,10 @@ pub fn parse_route_segments(
|
||||||
field.ty.clone()
|
field.ty.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
varient,
|
route_name,
|
||||||
format!(
|
format!(
|
||||||
"Could not find a field with the name '{}' in the variant '{}'",
|
"Could not find a field with the name '{}' in the variant '{}'",
|
||||||
query_ident, varient.ident
|
query_ident, route_name
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue