소스 검색

implement spread segments

Evan Almloff 2 년 전
부모
커밋
ee763d52e1
5개의 변경된 파일238개의 추가작업 그리고 123개의 파일을 삭제
  1. 22 6
      packages/router-core/src/router.rs
  2. 57 11
      packages/router-core/tests/macro.rs
  3. 2 4
      packages/router-macro/src/lib.rs
  4. 87 25
      packages/router-macro/src/route.rs
  5. 70 77
      packages/router-macro/src/route_tree.rs

+ 22 - 6
packages/router-core/src/router.rs

@@ -67,20 +67,36 @@ where
 }
 
 pub trait ToRouteSegments {
-    fn to_route_segments(&self) -> Vec<String>;
+    fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
+}
+
+impl<I, T: std::fmt::Display> ToRouteSegments for I
+where
+    I: IntoIterator<Item = T>,
+{
+    fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        for segment in self {
+            write!(f, "/")?;
+            write!(f, "{}", segment)?;
+        }
+        Ok(())
+    }
 }
 
 pub trait FromRouteSegments: Sized {
     type Err;
 
-    fn from_route_segments(segments: &[&str], query: &str) -> Result<Self, Self::Err>;
+    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
 }
 
-impl<T: FromRouteSegment> FromRouteSegments for Vec<T> {
-    type Err = <T as FromRouteSegment>::Err;
+impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
+    type Err = <String as FromRouteSegment>::Err;
 
-    fn from_route_segments(segments: &[&str], query: &str) -> Result<Self, Self::Err> {
-        segments.iter().map(|s| T::from_route_segment(s)).collect()
+    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
+        segments
+            .iter()
+            .map(|s| String::from_route_segment(s))
+            .collect()
     }
 }
 

+ 57 - 11
packages/router-core/tests/macro.rs

@@ -1,6 +1,6 @@
 use dioxus::prelude::*;
-use dioxus_router_macro::*;
 use dioxus_router_core::*;
+use dioxus_router_macro::*;
 use std::str::FromStr;
 
 #[inline_props]
@@ -43,7 +43,16 @@ fn Route4(cx: Scope, number1: u32, number2: u32) -> Element {
 fn Route5(cx: Scope, query: String) -> Element {
     render! {
         div{
-            "Route5"
+            "Route5: {query}"
+        }
+    }
+}
+
+#[inline_props]
+fn Route6(cx: Scope, extra: Vec<String>) -> Element {
+    render! {
+        div{
+            "Route5: {extra:?}"
         }
     }
 }
@@ -60,9 +69,9 @@ enum Route {
     #[route("/(number1)/(number2)" Route4)]
     Route4 { number1: u32, number2: u32 },
     #[route("/?(query)" Route5)]
-    Route5 {
-        query: String,
-    },
+    Route5 { query: String },
+    #[route("/(...extra)" Route6)]
+    Route6 { extra: Vec<String> },
 }
 
 #[test]
@@ -82,6 +91,25 @@ fn display_works() {
     };
 
     assert_eq!(route.to_string(), "/hello_world2");
+
+    let route = Route::Route4 {
+        number1: 1234,
+        number2: 5678,
+    };
+
+    assert_eq!(route.to_string(), "/1234/5678");
+
+    let route = Route::Route5 {
+        query: "hello".to_string(),
+    };
+
+    assert_eq!(route.to_string(), "/?hello");
+
+    let route = Route::Route6 {
+        extra: vec!["hello".to_string(), "world".to_string()],
+    };
+
+    assert_eq!(route.to_string(), "/hello/world");
 }
 
 #[test]
@@ -114,12 +142,6 @@ fn from_string_works() {
         })
     );
 
-    let w = "/hello_world/-1";
-    match Route::from_str(w) {
-        Ok(r) => panic!("should not parse {r:?}"),
-        Err(err) => println!("{err}"),
-    }
-
     let w = "/?x=1234&y=hello";
     assert_eq!(
         Route::from_str(w),
@@ -127,6 +149,18 @@ fn from_string_works() {
             query: "x=1234&y=hello".to_string()
         })
     );
+
+    let w = "/hello_world/hello_world/hello_world";
+    assert_eq!(
+        Route::from_str(w),
+        Ok(Route::Route6 {
+            extra: vec![
+                "hello_world".to_string(),
+                "hello_world".to_string(),
+                "hello_world".to_string()
+            ]
+        })
+    );
 }
 
 #[test]
@@ -161,4 +195,16 @@ fn round_trip() {
         query: string.to_string(),
     };
     assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+
+    // Route5
+    let route = Route::Route6 {
+        extra: vec![
+            "hello_world".to_string(),
+            "hello_world".to_string(),
+            "hello_world".to_string(),
+        ],
+    };
+    assert_eq!(Route::from_str(&route.to_string()), Ok(route));
 }
+
+fn main() {}

+ 2 - 4
packages/router-macro/src/lib.rs

@@ -17,7 +17,7 @@ pub fn derive_routable(input: TokenStream) -> TokenStream {
 
     let route_enum = match RouteEnum::parse(routes_enum) {
         Ok(route_enum) => route_enum,
-        Err(err) => return TokenStream2::from(err.to_compile_error()).into(),
+        Err(err) => return err.to_compile_error().into(),
     };
 
     let error_type = route_enum.error_type();
@@ -118,9 +118,7 @@ impl RouteEnum {
                     let mut segments = route.split('/');
                     let mut errors = Vec::new();
 
-                    if let Some(segment) = segments.next() {
-                        #(#tokens)*
-                    }
+                    #(#tokens)*
 
                     Err(RouteParseError {
                         attempted_routes: errors,

+ 87 - 25
packages/router-macro/src/route.rs

@@ -93,8 +93,7 @@ impl Route {
 
     pub fn routable_match(&self) -> TokenStream2 {
         let name = &self.route_name;
-        let dynamic_segments: Vec<_> = self
-            .dynamic_segments().collect();
+        let dynamic_segments: Vec<_> = self.dynamic_segments().collect();
         let props_name = &self.props_name;
         let comp_name = &self.comp_name;
 
@@ -110,7 +109,7 @@ impl Route {
         }
     }
 
-    fn dynamic_segments(&self)-> impl Iterator<Item = TokenStream2> +'_{
+    fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
         let segments = self.route_segments.iter().filter_map(|seg| {
             seg.name().map(|name| {
                 quote! {
@@ -118,18 +117,22 @@ impl Route {
                 }
             })
         });
-        let query = self.query.as_ref().map(|q| {
-            let name = q.name();
-            quote! {
-                #name
-            }
-    }).into_iter();
+        let query = self
+            .query
+            .as_ref()
+            .map(|q| {
+                let name = q.name();
+                quote! {
+                    #name
+                }
+            })
+            .into_iter();
 
         segments.chain(query)
     }
 
     pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
-       let segments = self.dynamic_segments();
+        let segments = self.dynamic_segments();
         let name = &self.route_name;
 
         quote! {
@@ -157,11 +160,14 @@ impl Route {
                     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) });
+                    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))? });
                 }
                 RouteSegment::CatchAll(ident, ty) => {
-                    error_variants.push(quote! { #error_name(<#ty as std::str::FromStr>::Err) });
+                    error_variants.push(quote! { #error_name(<#ty as dioxus_router_core::router::FromRouteSegments>::Err) });
                     display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
                 }
             }
@@ -195,6 +201,13 @@ impl Route {
             None => quote! {},
         }
     }
+
+    pub fn ends_with_catch_all(&self) -> bool {
+        self.route_segments
+            .last()
+            .map(|seg| matches!(seg, RouteSegment::CatchAll(..)))
+            .unwrap_or(false)
+    }
 }
 
 impl ToTokens for Route {
@@ -211,7 +224,7 @@ impl ToTokens for Route {
         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 != "" {
+        let route = if route.exists() && !without_leading_slash.is_empty() {
             with_extension.to_str().unwrap().to_string()
         } else {
             route_path.join("index.rs").to_str().unwrap().to_string()
@@ -259,13 +272,13 @@ fn parse_route_segments(
             let spread = segment.starts_with("(...");
 
             let ident = if spread {
-                segment[3..segment.len() - 1].to_string()
+                segment[4..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,
+                Some(ref field_ident) => *field_ident == ident,
                 None => false,
             });
 
@@ -361,7 +374,7 @@ impl RouteSegment {
         match self {
             Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
             Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
-            Self::CatchAll(ident, _) => quote! { write!(f, "/{}", #ident)?; },
+            Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; },
         }
     }
 
@@ -373,32 +386,81 @@ impl RouteSegment {
         }
     }
 
+    fn missing_error_name(&self) -> Option<Ident> {
+        match self {
+            Self::Dynamic(ident, _) => Some(format_ident!("{}MissingError", ident)),
+            _ => None,
+        }
+    }
+
     pub fn try_parse(
         &self,
         idx: usize,
         error_enum_name: &Ident,
         error_enum_varient: &Ident,
         inner_parse_enum: &Ident,
+        parse_children: TokenStream2,
     ) -> 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))
-                    };
+                    {
+                        let mut segments = segments.clone();
+                        let parsed = if let Some(#segment) = segments.next() {
+                            Ok(())
+                        } else {
+                            Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name))
+                        };
+                        match parsed {
+                            Ok(_) => {
+                                #parse_children
+                            }
+                            Err(err) => {
+                                errors.push(err);
+                            }
+                        }
+                    }
                 }
             }
-            Self::Dynamic(_, ty) => {
+            Self::Dynamic(name, ty) => {
+                let missing_error_name = self.missing_error_name().unwrap();
                 quote! {
-                    let parsed = <#ty as dioxus_router_core::router::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)));
+                    {
+                        let mut segments = segments.clone();
+                        let parsed = if let Some(segment) = segments.next() {
+                            <#ty as dioxus_router_core::router::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)))
+                        } else {
+                            Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#missing_error_name))
+                        };
+                        match parsed {
+                            Ok(#name) => {
+                                #parse_children
+                            }
+                            Err(err) => {
+                                errors.push(err);
+                            }
+                        }
+                    }
                 }
             }
-            Self::CatchAll(_, ty) => {
+            Self::CatchAll(name, ty) => {
                 quote! {
-                    let parsed = <#ty as dioxus_router_core::router::FromRouteSegments>::from_route_segments(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)));
+                    {
+                        let parsed = {
+                            let mut segments = segments.clone();
+                            let segments: Vec<_> = segments.collect();
+                            <#ty as dioxus_router_core::router::FromRouteSegments>::from_route_segments(&segments).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)))
+                        };
+                        match parsed {
+                            Ok(#name) => {
+                                #parse_children
+                            }
+                            Err(err) => {
+                                errors.push(err);
+                            }
+                        }
+                    }
                 }
             }
         }

+ 70 - 77
packages/router-macro/src/route_tree.rs

@@ -18,8 +18,8 @@ pub enum RouteTreeSegment<'a> {
 }
 
 impl<'a> RouteTreeSegment<'a> {
-    pub fn build(routes: &'a Vec<Route>) -> Vec<RouteTreeSegment<'a>> {
-        let routes = routes.into_iter().map(PartialRoute::new).collect();
+    pub fn build(routes: &'a [Route]) -> Vec<RouteTreeSegment<'a>> {
+        let routes = routes.iter().map(PartialRoute::new).collect();
         Self::construct(routes)
     }
 
@@ -37,7 +37,7 @@ impl<'a> RouteTreeSegment<'a> {
                             segment: s,
                             children,
                             ..
-                        } => (s == &segment).then(|| children),
+                        } => (s == &segment).then_some(children),
                         _ => None,
                     });
 
@@ -89,29 +89,22 @@ impl<'a> RouteTreeSegment<'a> {
                 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,
-                    });
+                let children = children
+                    .iter()
+                    .map(|child| child.to_tokens(enum_name.clone(), error_enum_name.clone()));
 
                 quote! {
-                    if #segment == segment {
+                    {
                         let mut segments = segments.clone();
-                        #(#children_without_next_segment)*
                         if let Some(segment) = segments.next() {
-                            #(#children_with_next_segment)*
+                            if #segment == segment {
+                                #(#children)*
+                            }
+                            else {
+                                errors.push(#error_enum_name::#enum_varient(#varient_parse_error::#error_ident))
+                            }
                         }
                     }
-                    else {
-                        errors.push(#error_enum_name::#enum_varient(#varient_parse_error::#error_ident))
-                    }
                 }
             }
             RouteTreeSegment::Dynamic(route) => {
@@ -123,50 +116,31 @@ impl<'a> RouteTreeSegment<'a> {
                     .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),
-                        )
-                    });
+                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
 
-                fn print_route_segment<I: Iterator<Item = (Option<Ident>, TokenStream)>>(
+                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((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
-                        };
+                    if let Some((i, route)) = s.next() {
+                        let children = print_route_segment(
+                            s,
+                            sucess_tokens,
+                            error_enum_name,
+                            enum_varient,
+                            varient_parse_error,
+                        );
 
-                        quote! {
-                            #first
-                            match parsed {
-                                Ok(#name) => {
-                                    #sucess
-                                }
-                                Err(err) => {
-                                    errors.push(err);
-                                }
-                            }
-                        }
+                        route.try_parse(
+                            i,
+                            error_enum_name,
+                            enum_varient,
+                            varient_parse_error,
+                            children,
+                        )
                     } else {
                         quote! {
                             #sucess_tokens
@@ -177,15 +151,25 @@ impl<'a> RouteTreeSegment<'a> {
                 let construct_variant = route.construct(enum_name);
                 let parse_query = route.parse_query();
 
+                let insure_not_trailing = route
+                    .route_segments
+                    .last()
+                    .map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
+                    .unwrap_or(true);
+
                 print_route_segment(
                     route_segments.peekable(),
                     return_constructed(
+                        insure_not_trailing,
                         construct_variant,
                         &error_enum_name,
                         enum_varient,
                         &varient_parse_error,
                         parse_query,
                     ),
+                    &error_enum_name,
+                    enum_varient,
+                    &varient_parse_error,
                 )
             }
             Self::StaticEnd(route) => {
@@ -195,6 +179,7 @@ impl<'a> RouteTreeSegment<'a> {
                 let parse_query = route.parse_query();
 
                 return_constructed(
+                    true,
                     construct_variant,
                     &error_enum_name,
                     enum_varient,
@@ -207,33 +192,41 @@ impl<'a> RouteTreeSegment<'a> {
 }
 
 fn return_constructed(
+    insure_not_trailing: bool,
     construct_variant: TokenStream,
     error_enum_name: &Ident,
     enum_varient: &Ident,
     varient_parse_error: &Ident,
     parse_query: TokenStream,
 ) -> 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) => {
-                #parse_query
-                return Ok(#construct_variant);
-            }
-            _ => {
-                let mut trailing = String::new();
-                for seg in remaining_segments {
-                    trailing += seg;
-                    trailing += "/";
+    if insure_not_trailing {
+        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) => {
+                    #parse_query
+                    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)))
                 }
-                trailing.pop();
-                errors.push(#error_enum_name::#enum_varient(#varient_parse_error::ExtraSegments(trailing)))
             }
         }
+    } else {
+        quote! {
+            #parse_query
+            return Ok(#construct_variant);
+        }
     }
 }