delete unneeded files
This commit is contained in:
1 parent
90c7e22f8b
commit
e26ff6a5c0
21 files changed
+990
-2537
No files matched your search
@@ -0,0 +1,206 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{__private::Span, format_ident, quote, ToTokens};
|
||||
use route::Route;
|
||||
use route_tree::RouteTreeSegment;
|
||||
use syn::{parse_macro_input, Ident};
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
mod route;
|
||||
mod route_tree;
|
||||
|
||||
#[proc_macro_derive(Routable, attributes(route))]
|
||||
pub fn derive_routable(input: TokenStream) -> TokenStream {
|
||||
let routes_enum = parse_macro_input!(input as syn::DeriveInput);
|
||||
|
||||
let route_enum = match RouteEnum::parse(routes_enum) {
|
||||
Ok(route_enum) => route_enum,
|
||||
Err(err) => return TokenStream2::from(err.to_compile_error()).into(),
|
||||
};
|
||||
|
||||
let error_type = route_enum.error_type();
|
||||
let parse_impl = route_enum.parse_impl();
|
||||
let display_impl = route_enum.impl_display();
|
||||
let routable_impl = route_enum.routable_impl();
|
||||
|
||||
quote! {
|
||||
#route_enum
|
||||
|
||||
#error_type
|
||||
|
||||
#parse_impl
|
||||
|
||||
#display_impl
|
||||
|
||||
#routable_impl
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
struct RouteEnum {
|
||||
route_name: Ident,
|
||||
routes: Vec<Route>,
|
||||
}
|
||||
|
||||
impl RouteEnum {
|
||||
fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
|
||||
let name = &input.ident;
|
||||
|
||||
if let syn::Data::Enum(data) = input.data {
|
||||
let mut routes = Vec::new();
|
||||
|
||||
for variant in data.variants {
|
||||
let route = Route::parse(variant)?;
|
||||
routes.push(route);
|
||||
}
|
||||
|
||||
let myself = Self {
|
||||
route_name: name.clone(),
|
||||
routes,
|
||||
};
|
||||
|
||||
Ok(myself)
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
input.clone(),
|
||||
"Routable can only be derived for enums",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_display(&self) -> TokenStream2 {
|
||||
let mut display_match = Vec::new();
|
||||
|
||||
for route in &self.routes {
|
||||
display_match.push(route.display_match());
|
||||
}
|
||||
|
||||
let name = &self.route_name;
|
||||
|
||||
quote! {
|
||||
impl std::fmt::Display for #name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#(#display_match)*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_impl(&self) -> TokenStream2 {
|
||||
let tree = RouteTreeSegment::build(&self.routes);
|
||||
let name = &self.route_name;
|
||||
|
||||
let error_name = format_ident!("{}MatchError", self.route_name);
|
||||
let tokens = tree
|
||||
.into_iter()
|
||||
.map(|t| t.to_tokens(self.route_name.clone(), error_name.clone()));
|
||||
|
||||
quote! {
|
||||
impl<'a> TryFrom<&'a str> for #name {
|
||||
type Error = <Self as std::str::FromStr>::Err;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||
s.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for #name {
|
||||
type Err = RouteParseError<#error_name>;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut segments = s.strip_prefix('/').unwrap_or(s).split('/');
|
||||
let mut errors = Vec::new();
|
||||
|
||||
if let Some(segment) = segments.next() {
|
||||
#(#tokens)*
|
||||
}
|
||||
|
||||
Err(RouteParseError {
|
||||
attempted_routes: errors,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn error_name(&self) -> Ident {
|
||||
Ident::new(
|
||||
&(self.route_name.to_string() + "MatchError"),
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
||||
|
||||
fn error_type(&self) -> TokenStream2 {
|
||||
let match_error_name = self.error_name();
|
||||
|
||||
let mut type_defs = Vec::new();
|
||||
let mut error_variants = Vec::new();
|
||||
let mut display_match = Vec::new();
|
||||
|
||||
for route in &self.routes {
|
||||
let route_name = &route.route_name;
|
||||
|
||||
let error_name = Ident::new(&format!("{}ParseError", route_name), Span::call_site());
|
||||
let route_str = &route.route;
|
||||
|
||||
error_variants.push(quote! { #route_name(#error_name) });
|
||||
display_match.push(quote! { Self::#route_name(err) => write!(f, "Route '{}' ('{}') did not match:\n{}", stringify!(#route_name), #route_str, err)? });
|
||||
type_defs.push(route.error_type());
|
||||
}
|
||||
|
||||
quote! {
|
||||
#(#type_defs)*
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum #match_error_name {
|
||||
#(#error_variants),*
|
||||
}
|
||||
|
||||
impl std::fmt::Display for #match_error_name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#(#display_match),*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn routable_impl(&self) -> TokenStream2 {
|
||||
let mut routable_match = Vec::new();
|
||||
|
||||
for route in &self.routes {
|
||||
routable_match.push(route.routable_match());
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl Routable for Route {
|
||||
fn render<'a>(self, cx: &'a ScopeState) -> Element<'a> {
|
||||
match self {
|
||||
#(#routable_match)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for RouteEnum {
|
||||
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
||||
let routes = &self.routes;
|
||||
|
||||
tokens.extend(quote!(
|
||||
#[path = "pages"]
|
||||
mod pages {
|
||||
#(#routes)*
|
||||
}
|
||||
pub use pages::*;
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
use quote::{__private::Span, format_ident, quote, ToTokens};
|
||||
use syn::{Ident, LitStr, Type, Variant};
|
||||
use syn::parse::ParseStream;
|
||||
use syn::parse::Parse;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
struct RouteArgs{
|
||||
route: LitStr,
|
||||
comp_name: Option<Ident>,
|
||||
props_name: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for RouteArgs{
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self>{
|
||||
let route = input.parse::<LitStr>()?;
|
||||
|
||||
Ok(RouteArgs {
|
||||
route,
|
||||
comp_name:input.parse().ok(),
|
||||
props_name: input.parse().ok(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Route {
|
||||
pub file_based: bool,
|
||||
pub route_name: Ident,
|
||||
pub comp_name: Ident,
|
||||
pub props_name: Ident,
|
||||
pub route: LitStr,
|
||||
pub route_segments: Vec<RouteSegment>,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn parse(input: syn::Variant) -> syn::Result<Self> {
|
||||
let route_attr = input
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path.is_ident("route"))
|
||||
.ok_or_else(|| {
|
||||
syn::Error::new_spanned(
|
||||
input.clone(),
|
||||
"Routable variants must have a #[route(...)] attribute",
|
||||
)
|
||||
})?;
|
||||
|
||||
let route_name = input.ident.clone();
|
||||
let args = route_attr.parse_args::<RouteArgs>()?;
|
||||
let route = args.route;
|
||||
let file_based= args.comp_name.is_none();
|
||||
let comp_name = args.comp_name.unwrap_or_else(|| format_ident!("{}", route_name));
|
||||
let props_name = args.props_name.unwrap_or_else(|| format_ident!("{}Props", comp_name));
|
||||
|
||||
let route_segments = parse_route_segments(&input, &route)?;
|
||||
|
||||
Ok(Self {
|
||||
comp_name,
|
||||
props_name,
|
||||
route_name,
|
||||
route_segments,
|
||||
route,
|
||||
file_based,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn display_match(&self) -> TokenStream2 {
|
||||
let name = &self.route_name;
|
||||
let dynamic_segments = self.route_segments.iter().filter_map(|s| s.name());
|
||||
let write_segments = self.route_segments.iter().map(|s| s.write_segment());
|
||||
|
||||
quote! {
|
||||
Self::#name { #(#dynamic_segments,)* } => {
|
||||
#(#write_segments)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routable_match(&self) -> TokenStream2 {
|
||||
let name = &self.route_name;
|
||||
let dynamic_segments: Vec<_> = self
|
||||
.route_segments
|
||||
.iter()
|
||||
.filter_map(|s| s.name())
|
||||
.collect();
|
||||
let props_name = &self.props_name;
|
||||
let comp_name = &self.comp_name;
|
||||
|
||||
quote! {
|
||||
Self::#name { #(#dynamic_segments,)* } => {
|
||||
let comp = #props_name { #(#dynamic_segments,)* };
|
||||
let cx = cx.bump().alloc(Scoped {
|
||||
props: cx.bump().alloc(comp),
|
||||
scope: cx,
|
||||
});
|
||||
#comp_name(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
|
||||
let segments = self.route_segments.iter().filter_map(|seg| {
|
||||
seg.name().map(|name| {
|
||||
quote! {
|
||||
#name
|
||||
}
|
||||
})
|
||||
});
|
||||
let name = &self.route_name;
|
||||
|
||||
quote! {
|
||||
#enum_name::#name {
|
||||
#(#segments,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_ident(&self) -> Ident {
|
||||
format_ident!("{}ParseError", self.route_name)
|
||||
}
|
||||
|
||||
pub fn error_type(&self) -> TokenStream2 {
|
||||
let error_name = self.error_ident();
|
||||
|
||||
let mut error_variants = Vec::new();
|
||||
let mut display_match = Vec::new();
|
||||
|
||||
for (i, segment) in self.route_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) => {
|
||||
error_variants.push(quote! { #error_name(<#ty as std::str::FromStr>::Err) });
|
||||
display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
|
||||
}
|
||||
RouteSegment::CatchAll(ident, ty) => {
|
||||
error_variants.push(quote! { #error_name(<#ty as std::str::FromStr>::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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Route {
|
||||
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
||||
if !self.file_based {
|
||||
return;
|
||||
}
|
||||
|
||||
let without_leading_slash = &self.route.value()[1..];
|
||||
let route_path = std::path::Path::new(without_leading_slash);
|
||||
let with_extension = route_path.with_extension("rs");
|
||||
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let dir = std::path::Path::new(&dir);
|
||||
let route = dir.join("src").join("pages").join(with_extension.clone());
|
||||
|
||||
// check if the route exists or if not use the index route
|
||||
let route = if route.exists() && without_leading_slash != "" {
|
||||
with_extension.to_str().unwrap().to_string()
|
||||
} else {
|
||||
route_path.join("index.rs").to_str().unwrap().to_string()
|
||||
};
|
||||
|
||||
let route_name: Ident = self.route_name.clone();
|
||||
let prop_name = &self.props_name;
|
||||
|
||||
tokens.extend(quote!(
|
||||
#[path = #route]
|
||||
#[allow(non_snake_case)]
|
||||
mod #route_name;
|
||||
pub use #route_name::{#prop_name, #route_name};
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_route_segments(varient: &Variant, route: &LitStr) -> syn::Result<Vec<RouteSegment>> {
|
||||
let mut route_segments = Vec::new();
|
||||
|
||||
let route_string = route.value();
|
||||
let mut iterator = route_string.split('/');
|
||||
|
||||
// skip the first empty segment
|
||||
let first = iterator.next();
|
||||
if first != Some("") {
|
||||
return Err(syn::Error::new_spanned(
|
||||
varient,
|
||||
format!(
|
||||
"Routes should start with /. Error found in the route '{}'",
|
||||
route.value()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
while let Some(segment) = iterator.next() {
|
||||
if segment.starts_with('(') && segment.ends_with(')') {
|
||||
let spread = segment.starts_with("(...");
|
||||
|
||||
let ident = if spread {
|
||||
segment[3..segment.len() - 1].to_string()
|
||||
} else {
|
||||
segment[1..segment.len() - 1].to_string()
|
||||
};
|
||||
|
||||
let field = varient.fields.iter().find(|field| match field.ident {
|
||||
Some(ref field_ident) => field_ident.to_string() == ident,
|
||||
None => false,
|
||||
});
|
||||
|
||||
let ty = if let Some(field) = field {
|
||||
field.ty.clone()
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
varient,
|
||||
format!(
|
||||
"Could not find a field with the name '{}' in the variant '{}'",
|
||||
ident, varient.ident
|
||||
),
|
||||
));
|
||||
};
|
||||
if spread {
|
||||
route_segments.push(RouteSegment::CatchAll(
|
||||
Ident::new(&ident, Span::call_site()),
|
||||
ty,
|
||||
));
|
||||
|
||||
if iterator.next().is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
route,
|
||||
"Catch-all route segments must be the last segment in a route. The route segments after the catch-all segment will never be matched.",
|
||||
));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
route_segments.push(RouteSegment::Dynamic(
|
||||
Ident::new(&ident, Span::call_site()),
|
||||
ty,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
route_segments.push(RouteSegment::Static(segment.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(route_segments)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RouteSegment {
|
||||
Static(String),
|
||||
Dynamic(Ident, Type),
|
||||
CatchAll(Ident, Type),
|
||||
}
|
||||
|
||||
impl RouteSegment {
|
||||
pub fn name(&self) -> Option<Ident> {
|
||||
match self {
|
||||
Self::Static(_) => None,
|
||||
Self::Dynamic(ident, _) => Some(ident.clone()),
|
||||
Self::CatchAll(ident, _) => Some(ident.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_segment(&self) -> TokenStream2 {
|
||||
match self {
|
||||
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
||||
Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
|
||||
Self::CatchAll(ident, _) => quote! { write!(f, "/{}", #ident)?; },
|
||||
}
|
||||
}
|
||||
|
||||
fn error_name(&self, idx: usize) -> Ident {
|
||||
match self {
|
||||
Self::Static(_) => static_segment_idx(idx),
|
||||
Self::Dynamic(ident, _) => format_ident!("{}ParseError", ident),
|
||||
Self::CatchAll(ident, _) => format_ident!("{}ParseError", ident),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_parse(
|
||||
&self,
|
||||
idx: usize,
|
||||
error_enum_name: &Ident,
|
||||
error_enum_varient: &Ident,
|
||||
inner_parse_enum: &Ident,
|
||||
) -> TokenStream2 {
|
||||
let error_name = self.error_name(idx);
|
||||
match self {
|
||||
Self::Static(segment) => {
|
||||
quote! {
|
||||
let parsed = if segment == #segment {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name))
|
||||
};
|
||||
}
|
||||
}
|
||||
Self::Dynamic(_, ty) => {
|
||||
quote! {
|
||||
let parsed = <#ty as std::str::FromStr>::from_str(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)));
|
||||
}
|
||||
}
|
||||
Self::CatchAll(_, _) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn static_segment_idx(idx: usize) -> Ident {
|
||||
format_ident!("StaticSegment{}ParseError", idx)
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::route::{static_segment_idx, Route, RouteSegment};
|
||||
|
||||
// First deduplicate the routes by the static part of the route
|
||||
#[derive(Debug)]
|
||||
pub enum RouteTreeSegment<'a> {
|
||||
Static {
|
||||
index: usize,
|
||||
segment: &'a str,
|
||||
children: Vec<RouteTreeSegment<'a>>,
|
||||
from_route: &'a Route,
|
||||
},
|
||||
Dynamic(&'a Route),
|
||||
StaticEnd(&'a Route),
|
||||
}
|
||||
|
||||
impl<'a> RouteTreeSegment<'a> {
|
||||
pub fn build(routes: &'a Vec<Route>) -> Vec<RouteTreeSegment<'a>> {
|
||||
let routes = routes.into_iter().map(PartialRoute::new).collect();
|
||||
Self::construct(routes)
|
||||
}
|
||||
|
||||
fn construct(routes: Vec<PartialRoute<'a>>) -> Vec<RouteTreeSegment<'a>> {
|
||||
let mut static_segments = Vec::new();
|
||||
let mut dyn_segments = Vec::new();
|
||||
|
||||
// Add all routes we can to the tree
|
||||
for mut route in routes {
|
||||
match route.next_static_segment() {
|
||||
// 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(|| children),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
match found {
|
||||
Some(children) => {
|
||||
// If it does, add the route to the children of the segment
|
||||
children.append(&mut RouteTreeSegment::construct(vec![route]))
|
||||
}
|
||||
None => {
|
||||
// If it doesn't, add the route as a new segment
|
||||
static_segments.push(RouteTreeSegment::Static {
|
||||
segment,
|
||||
from_route: route.route,
|
||||
children: RouteTreeSegment::construct(vec![route]),
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there is no static segment, add the route to the dynamic routes
|
||||
None => {
|
||||
// This route is entirely static
|
||||
if route.route.route_segments.len() == route.static_segment_index {
|
||||
static_segments.push(RouteTreeSegment::StaticEnd(route.route));
|
||||
} else {
|
||||
dyn_segments.push(RouteTreeSegment::Dynamic(route.route));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All static routes are checked before dynamic routes
|
||||
static_segments.append(&mut dyn_segments);
|
||||
|
||||
static_segments
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RouteTreeSegment<'a> {
|
||||
pub fn to_tokens(&self, enum_name: syn::Ident, error_enum_name: syn::Ident) -> TokenStream {
|
||||
match self {
|
||||
RouteTreeSegment::Static {
|
||||
segment,
|
||||
children,
|
||||
index,
|
||||
from_route,
|
||||
} => {
|
||||
let varient_parse_error = from_route.error_ident();
|
||||
let enum_varient = &from_route.route_name;
|
||||
let error_ident = static_segment_idx(*index);
|
||||
|
||||
let children_with_next_segment = children.iter().filter_map(|child| match child {
|
||||
RouteTreeSegment::StaticEnd { .. } => None,
|
||||
_ => Some(child.to_tokens(enum_name.clone(), error_enum_name.clone())),
|
||||
});
|
||||
let children_without_next_segment =
|
||||
children.iter().filter_map(|child| match child {
|
||||
RouteTreeSegment::StaticEnd { .. } => {
|
||||
Some(child.to_tokens(enum_name.clone(), error_enum_name.clone()))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
quote! {
|
||||
if #segment == segment {
|
||||
let mut segments = segments.clone();
|
||||
#(#children_without_next_segment)*
|
||||
if let Some(segment) = segments.next() {
|
||||
#(#children_with_next_segment)*
|
||||
}
|
||||
}
|
||||
else {
|
||||
errors.push(#error_enum_name::#enum_varient(#varient_parse_error::#error_ident))
|
||||
}
|
||||
}
|
||||
}
|
||||
RouteTreeSegment::Dynamic(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 enum_varient = &route.route_name;
|
||||
|
||||
let route_segments = route
|
||||
.route_segments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip_while(|(_, seg)| match seg {
|
||||
RouteSegment::Static(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.map(|(i, seg)| {
|
||||
(
|
||||
seg.name(),
|
||||
seg.try_parse(i, &error_enum_name, enum_varient, &varient_parse_error),
|
||||
)
|
||||
});
|
||||
|
||||
fn print_route_segment<I: Iterator<Item = (Option<Ident>, TokenStream)>>(
|
||||
mut s: std::iter::Peekable<I>,
|
||||
sucess_tokens: TokenStream,
|
||||
) -> TokenStream {
|
||||
if let Some((name, first)) = s.next() {
|
||||
let has_next = s.peek().is_some();
|
||||
let children = print_route_segment(s, sucess_tokens);
|
||||
let name = name
|
||||
.map(|name| quote! {#name})
|
||||
.unwrap_or_else(|| quote! {_});
|
||||
|
||||
let sucess = if has_next {
|
||||
quote! {
|
||||
let mut segments = segments.clone();
|
||||
if let Some(segment) = segments.next() {
|
||||
#children
|
||||
}
|
||||
}
|
||||
} else {
|
||||
children
|
||||
};
|
||||
|
||||
quote! {
|
||||
#first
|
||||
match parsed {
|
||||
Ok(#name) => {
|
||||
#sucess
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#sucess_tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let construct_variant = route.construct(enum_name);
|
||||
|
||||
print_route_segment(
|
||||
route_segments.peekable(),
|
||||
return_constructed(
|
||||
construct_variant,
|
||||
&error_enum_name,
|
||||
enum_varient,
|
||||
&varient_parse_error,
|
||||
),
|
||||
)
|
||||
}
|
||||
Self::StaticEnd(route) => {
|
||||
let varient_parse_error = route.error_ident();
|
||||
let enum_varient = &route.route_name;
|
||||
let construct_variant = route.construct(enum_name);
|
||||
|
||||
return_constructed(
|
||||
construct_variant,
|
||||
&error_enum_name,
|
||||
enum_varient,
|
||||
&varient_parse_error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn return_constructed(
|
||||
construct_variant: TokenStream,
|
||||
error_enum_name: &Ident,
|
||||
enum_varient: &Ident,
|
||||
varient_parse_error: &Ident,
|
||||
) -> TokenStream {
|
||||
quote! {
|
||||
let remaining_segments = segments.clone();
|
||||
let mut segments_clone = segments.clone();
|
||||
let next_segment = segments_clone.next();
|
||||
let segment_after_next = segments_clone.next();
|
||||
match (next_segment, segment_after_next) {
|
||||
// This is the last segment, return the parsed route
|
||||
(None, _) | (Some(""), None) => {
|
||||
return Ok(#construct_variant);
|
||||
}
|
||||
_ => {
|
||||
let mut trailing = String::new();
|
||||
for seg in remaining_segments {
|
||||
trailing += seg;
|
||||
trailing += "/";
|
||||
}
|
||||
trailing.pop();
|
||||
errors.push(#error_enum_name::#enum_varient(#varient_parse_error::ExtraSegments(trailing)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PartialRoute<'a> {
|
||||
route: &'a Route,
|
||||
static_segment_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> PartialRoute<'a> {
|
||||
fn new(route: &'a Route) -> Self {
|
||||
Self {
|
||||
route,
|
||||
static_segment_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {
|
||||
let idx = self.static_segment_index;
|
||||
let segment = self.route.route_segments.get(idx)?;
|
||||
match segment {
|
||||
RouteSegment::Static(segment) => {
|
||||
self.static_segment_index += 1;
|
||||
Some((idx, segment))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user