瀏覽代碼

parse query strings

Evan Almloff 2 年之前
父節點
當前提交
2aadeb8046

+ 1 - 0
Cargo.toml

@@ -4,6 +4,7 @@ members = [
     "packages/core",
     "packages/core-macro",
     "packages/router-core",
+    "packages/router-macro",
     "packages/router",
     "packages/html",
     "packages/hooks",

+ 2 - 0
packages/router-core/Cargo.toml

@@ -20,6 +20,8 @@ url = "2.3.1"
 urlencoding = "2.1.2"
 wasm-bindgen = { version = "0.2.83", optional = true }
 web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"]}
+dioxus = { path = "../dioxus" }
+dioxus-router-macro = { path = "../router-macro" }
 
 [features]
 regex = ["dep:regex"]

+ 14 - 14
packages/router-core/src/lib.rs

@@ -1,30 +1,30 @@
 #![doc = include_str!("../README.md")]
-#![forbid(missing_docs)]
+// #![forbid(missing_docs)]
 
 pub mod history;
 
-mod router;
+pub mod router;
 pub use router::*;
 
-pub mod navigation;
+// pub mod navigation;
 
-mod navigator;
-pub use navigator::*;
+// mod navigator;
+// pub use navigator::*;
 
-mod service;
-pub use service::*;
+// mod service;
+// pub use service::*;
 
-mod state;
-pub use state::*;
+// mod state;
+// pub use state::*;
 
-mod utils {
-    mod sitemap;
-    pub use sitemap::*;
-}
+// mod utils {
+//     mod sitemap;
+//     pub use sitemap::*;
+// }
 
 /// A collection of useful types most applications might need.
 pub mod prelude {
-    pub use crate::navigation::*;
+    // pub use crate::navigation::*;
 
     /// An external navigation failure.
     ///

+ 2 - 40
packages/router-core/src/navigation.rs

@@ -6,7 +6,7 @@ use url::{ParseError, Url};
 
 /// A target for the router to navigate to.
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub enum NavigationTarget {
+pub enum NavigationTarget<R: Routable> {
     /// An internal path that the router can navigate to by itself.
     ///
     /// ```rust
@@ -15,45 +15,7 @@ pub enum NavigationTarget {
     /// let implicit: NavigationTarget = "/internal".into();
     /// assert_eq!(explicit, implicit);
     /// ```
-    Internal(String),
-    /// An internal target that the router can navigate to by itself.
-    ///
-    /// ```rust
-    /// # use std::collections::HashMap;
-    /// # use dioxus_router_core::{Name, navigation::{named, NavigationTarget}};
-    /// let mut parameters = HashMap::new();
-    /// parameters.insert(Name::of::<bool>(), String::from("some parameter"));
-    ///
-    /// let explicit = NavigationTarget::Named {
-    ///     name: Name::of::<bool>(),
-    ///     parameters,
-    ///     query: Some("some=query".into())
-    /// };
-    ///
-    /// let implicit = named::<bool>().parameter::<bool>("some parameter").query("some=query");
-    ///
-    /// assert_eq!(explicit, implicit);
-    /// ```
-    ///
-    /// It will automatically find the route with the matching name, insert all required parameters
-    /// and add the query.
-    ///
-    /// **Note:** The dioxus-router-core documentation and tests mostly use standard Rust types. This is only
-    /// for brevity. It is recommend to use types with descriptive names, and create unit structs if
-    /// needed.
-    Named {
-        /// The name of the [`Route`](crate::routes::Route) or
-        /// [`ParameterRoute`](crate::routes::ParameterRoute) to navigate to.
-        ///
-        /// **Note:** The dioxus-router-core documentation and tests mostly use standard Rust types. This is
-        /// only for brevity. It is recommend to use types with descriptive names, and create unit
-        /// structs if needed.
-        name: Name,
-        /// The parameters required to get to the specified route.
-        parameters: HashMap<Name, String>,
-        /// A query to add to the route.
-        query: Option<Query>,
-    },
+    Internal(R),
     /// An external target that the router doesn't control.
     ///
     /// ```rust

+ 58 - 107
packages/router-core/src/router.rs

@@ -1,9 +1,11 @@
 use crate::history::HistoryProvider;
+use dioxus::prelude::*;
+
 use std::str::FromStr;
 
 #[derive(Debug, PartialEq)]
-struct RouteParseError<E: std::fmt::Display> {
-    attempted_routes: Vec<E>,
+pub struct RouteParseError<E: std::fmt::Display> {
+    pub attempted_routes: Vec<E>,
 }
 
 impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
@@ -37,127 +39,76 @@ where
     }
 }
 
-// #[derive(Props, PartialEq)]
-// struct RouterProps {
-//     current_route: String,
-// }
-
-trait Routable: FromStr + std::fmt::Display + Clone
-where
-    <Self as FromStr>::Err: std::fmt::Display,
-{
-//     fn render(self, cx: &ScopeState) -> Element;
-
-//     fn comp(cx: Scope<RouterProps>) -> Element
-//     where
-//         Self: 'static,
-//     {
-//         let router = Self::from_str(&cx.props.current_route);
-//         match router {
-//             Ok(router) => router.render(cx),
-//             Err(err) => {
-//                 render! {pre {
-//                     "{err}"
-//                 }}
-//             }
-//         }
-//     }
+pub trait FromQuery {
+    fn from_query(query: &str) -> Self;
 }
 
-#[derive(Routable, Clone, Debug, PartialEq)]
-enum Route {
-    #[route("/(dynamic)")]
-    Route1 { dynamic: String },
-    #[route("/hello_world")]
-    Route2 {},
-    #[redirect("/(dynamic)/hello_world")]
-    #[route("/hello_world/(dynamic)")]
-    Route3 { dynamic: u32 },
-    #[route("/(number1)/(number2)")]
-    Route4 { number1: u32, number2: u32 },
-    #[route("/")]
-    Route5 {},
+impl<T: for<'a> From<&'a str>> FromQuery for T {
+    fn from_query(query: &str) -> Self {
+        T::from(query)
+    }
 }
 
-#[test]
-fn display_works() {
-    let route = Route::Route1 {
-        dynamic: "hello".to_string(),
-    };
+pub trait FromRouteSegment: Sized {
+    type Err;
 
-    assert_eq!(route.to_string(), "/hello");
+    fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
+}
 
-    let route = Route::Route3 { dynamic: 1234 };
+impl<T: FromStr> FromRouteSegment for T
+where
+    <T as FromStr>::Err: std::fmt::Display,
+{
+    type Err = <T as FromStr>::Err;
 
-    assert_eq!(route.to_string(), "/hello_world/1234");
+    fn from_route_segment(route: &str) -> Result<Self, Self::Err> {
+        T::from_str(route)
+    }
+}
 
-    let route = Route::Route1 {
-        dynamic: "hello_world2".to_string(),
-    };
+pub trait ToRouteSegments {
+    fn to_route_segments(&self) -> Vec<String>;
+}
+
+pub trait FromRouteSegments: Sized {
+    type Err;
 
-    assert_eq!(route.to_string(), "/hello_world2");
+    fn from_route_segments(segments: &[&str], query: &str) -> Result<Self, Self::Err>;
 }
 
-#[test]
-fn from_string_works() {
-    let w = "/hello";
-    assert_eq!(
-        Route::from_str(w),
-        Ok(Route::Route1 {
-            dynamic: "hello".to_string()
-        })
-    );
-    let w = "/hello/";
-    assert_eq!(
-        Route::from_str(w),
-        Ok(Route::Route1 {
-            dynamic: "hello".to_string()
-        })
-    );
-
-    let w = "/hello_world/1234";
-    assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 }));
-    let w = "/hello_world/1234/";
-    assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 }));
-
-    let w = "/hello_world2";
-    assert_eq!(
-        Route::from_str(w),
-        Ok(Route::Route1 {
-            dynamic: "hello_world2".to_string()
-        })
-    );
+impl<T: FromRouteSegment> FromRouteSegments for Vec<T> {
+    type Err = <T as FromRouteSegment>::Err;
 
-    let w = "/hello_world/-1";
-    match Route::from_str(w) {
-        Ok(r) => panic!("should not parse {r:?}"),
-        Err(err) => println!("{err}"),
+    fn from_route_segments(segments: &[&str], query: &str) -> Result<Self, Self::Err> {
+        segments.iter().map(|s| T::from_route_segment(s)).collect()
     }
 }
 
-#[test]
-fn round_trip() {
-    // Route1
-    let string = "hello_world2";
-    let route = Route::Route1 {
-        dynamic: string.to_string(),
-    };
-    assert_eq!(Route::from_str(&route.to_string()), Ok(route));
-
-    // Route2
-    for num in 0..100 {
-        let route = Route::Route3 { dynamic: num };
-        assert_eq!(Route::from_str(&route.to_string()), Ok(route));
-    }
+#[derive(Props, PartialEq)]
+pub struct RouterProps {
+    pub current_route: String,
+}
 
-    // Route3
-    for num1 in 0..100 {
-        for num2 in 0..100 {
-            let route = Route::Route4 {
-                number1: num1,
-                number2: num2,
-            };
-            assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+pub trait Routable: FromStr + std::fmt::Display + Clone
+where
+    <Self as FromStr>::Err: std::fmt::Display,
+{
+    fn render(self, cx: &ScopeState) -> Element;
+
+    fn comp(cx: Scope<RouterProps>) -> Element
+    where
+        Self: 'static,
+    {
+        let router = Self::from_str(&cx.props.current_route);
+        match router {
+            Ok(router) => router.render(cx),
+            Err(err) => {
+                render! {
+                    pre {
+                        "{err}"
+                    }
+                }
+            }
         }
     }
 }

+ 5 - 8
packages/router-core/src/service.rs

@@ -15,18 +15,15 @@ use crate::{
     prelude::{
         FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit, RootIndex,
     },
-    routes::{ContentAtom, Segment},
-    segments::{NameMap, NamedSegment},
-    utils::{resolve_target, route_segment},
-    Name, RouterState,
+    RouterState,
 };
 
 /// Messages that the [`RouterService`] can handle.
-pub enum RouterMessage<I> {
+pub enum RouterMessage<I, R> {
     /// Subscribe to router update.
     Subscribe(Arc<I>),
     /// Navigate to the specified target.
-    Push(NavigationTarget),
+    Push(NavigationTarget<R>),
     /// Replace the current location with the specified target.
     Replace(NavigationTarget),
     /// Trigger a routing update.
@@ -63,9 +60,9 @@ impl<I: PartialEq> PartialEq for RouterMessage<I> {
 
 impl<I: Eq> Eq for RouterMessage<I> {}
 
-enum NavigationFailure {
+enum NavigationFailure<R: Routable> {
     External(String),
-    Named(Name),
+    Internal(<R as std::str::FromStr>::Err),
 }
 
 /// A function the router will call after every routing update.

+ 1 - 1
packages/router-core/src/state.rs

@@ -153,7 +153,7 @@ impl<T: Clone> Default for RouterState<T> {
 
 #[cfg(test)]
 mod tests {
-    use crate::{navigation::named, prelude::RootIndex, routes::Segment, segments::NamedSegment};
+    use crate::{navigation::named, prelude::RootIndex};
 
     use super::*;
 

+ 0 - 1
packages/router-core/src/utils/sitemap.rs

@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
 
 use urlencoding::encode;
 
-
 pub fn gen_sitemap<T: Clone>(seg: &Segment<T>, current: &str, map: &mut Vec<String>) {
     for (p, r) in &seg.fixed {
         let current = format!("{current}/{p}");

+ 164 - 0
packages/router-core/tests/macro.rs

@@ -0,0 +1,164 @@
+use dioxus::prelude::*;
+use dioxus_router_macro::*;
+use dioxus_router_core::*;
+use std::str::FromStr;
+
+#[inline_props]
+fn Route1(cx: Scope, dynamic: String) -> Element {
+    render! {
+        div{
+            "Route1: {dynamic}"
+        }
+    }
+}
+
+#[inline_props]
+fn Route2(cx: Scope) -> Element {
+    render! {
+        div{
+            "Route2"
+        }
+    }
+}
+
+#[inline_props]
+fn Route3(cx: Scope, dynamic: u32) -> Element {
+    render! {
+        div{
+            "Route3: {dynamic}"
+        }
+    }
+}
+
+#[inline_props]
+fn Route4(cx: Scope, number1: u32, number2: u32) -> Element {
+    render! {
+        div{
+            "Route4: {number1} {number2}"
+        }
+    }
+}
+
+#[inline_props]
+fn Route5(cx: Scope, query: String) -> Element {
+    render! {
+        div{
+            "Route5"
+        }
+    }
+}
+
+#[derive(Routable, Clone, Debug, PartialEq)]
+enum Route {
+    #[route("/(dynamic)" Route1)]
+    Route1 { dynamic: String },
+    #[route("/hello_world" Route2)]
+    Route2 {},
+    // #[redirect("/(dynamic)/hello_world")]
+    #[route("/hello_world/(dynamic)" Route3)]
+    Route3 { dynamic: u32 },
+    #[route("/(number1)/(number2)" Route4)]
+    Route4 { number1: u32, number2: u32 },
+    #[route("/?(query)" Route5)]
+    Route5 {
+        query: String,
+    },
+}
+
+#[test]
+fn display_works() {
+    let route = Route::Route1 {
+        dynamic: "hello".to_string(),
+    };
+
+    assert_eq!(route.to_string(), "/hello");
+
+    let route = Route::Route3 { dynamic: 1234 };
+
+    assert_eq!(route.to_string(), "/hello_world/1234");
+
+    let route = Route::Route1 {
+        dynamic: "hello_world2".to_string(),
+    };
+
+    assert_eq!(route.to_string(), "/hello_world2");
+}
+
+#[test]
+fn from_string_works() {
+    let w = "/hello";
+    assert_eq!(
+        Route::from_str(w),
+        Ok(Route::Route1 {
+            dynamic: "hello".to_string()
+        })
+    );
+    let w = "/hello/";
+    assert_eq!(
+        Route::from_str(w),
+        Ok(Route::Route1 {
+            dynamic: "hello".to_string()
+        })
+    );
+
+    let w = "/hello_world/1234";
+    assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 }));
+    let w = "/hello_world/1234/";
+    assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 }));
+
+    let w = "/hello_world2";
+    assert_eq!(
+        Route::from_str(w),
+        Ok(Route::Route1 {
+            dynamic: "hello_world2".to_string()
+        })
+    );
+
+    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),
+        Ok(Route::Route5 {
+            query: "x=1234&y=hello".to_string()
+        })
+    );
+}
+
+#[test]
+fn round_trip() {
+    // Route1
+    let string = "hello_world2";
+    let route = Route::Route1 {
+        dynamic: string.to_string(),
+    };
+    assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+
+    // Route2
+    for num in 0..100 {
+        let route = Route::Route3 { dynamic: num };
+        assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+    }
+
+    // Route3
+    for num1 in 0..100 {
+        for num2 in 0..100 {
+            let route = Route::Route4 {
+                number1: num1,
+                number2: num2,
+            };
+            assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+        }
+    }
+
+    // Route4
+    let string = "x=1234&y=hello";
+    let route = Route::Route5 {
+        query: string.to_string(),
+    };
+    assert_eq!(Route::from_str(&route.to_string()), Ok(route));
+}

+ 3 - 1
packages/router-macro/src/lib.rs

@@ -113,7 +113,9 @@ impl RouteEnum {
                 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 route = s.strip_prefix('/').unwrap_or(s);
+                    let (route, query) = route.split_once('?').unwrap_or((route, ""));
+                    let mut segments = route.split('/');
                     let mut errors = Vec::new();
 
                     if let Some(segment) = segments.next() {

+ 122 - 23
packages/router-macro/src/route.rs

@@ -1,24 +1,30 @@
 use quote::{__private::Span, format_ident, quote, ToTokens};
-use syn::{Ident, LitStr, Type, Variant};
-use syn::parse::ParseStream;
 use syn::parse::Parse;
+use syn::parse::ParseStream;
+use syn::{Ident, LitStr, Type, Variant};
 
 use proc_macro2::TokenStream as TokenStream2;
 
-struct RouteArgs{
+struct RouteArgs {
     route: LitStr,
     comp_name: Option<Ident>,
     props_name: Option<Ident>,
 }
 
-impl Parse for RouteArgs{
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self>{
+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(),
+            comp_name: {
+                let _ = input.parse::<syn::Token![,]>();
+                input.parse().ok()
+            },
+            props_name: {
+                let _ = input.parse::<syn::Token![,]>();
+                input.parse().ok()
+            },
         })
     }
 }
@@ -31,6 +37,7 @@ pub struct Route {
     pub props_name: Ident,
     pub route: LitStr,
     pub route_segments: Vec<RouteSegment>,
+    pub query: Option<QuerySegment>,
 }
 
 impl Route {
@@ -49,11 +56,15 @@ impl Route {
         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 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)?;
+        let (route_segments, query) = parse_route_segments(&input, &route)?;
 
         Ok(Self {
             comp_name,
@@ -62,17 +73,20 @@ impl Route {
             route_segments,
             route,
             file_based,
+            query,
         })
     }
 
     pub fn display_match(&self) -> TokenStream2 {
         let name = &self.route_name;
-        let dynamic_segments = self.route_segments.iter().filter_map(|s| s.name());
+        let dynamic_segments = self.dynamic_segments();
         let write_segments = self.route_segments.iter().map(|s| s.write_segment());
+        let write_query = self.query.as_ref().map(|q| q.write());
 
         quote! {
             Self::#name { #(#dynamic_segments,)* } => {
                 #(#write_segments)*
+                #write_query
             }
         }
     }
@@ -80,10 +94,7 @@ impl Route {
     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();
+            .dynamic_segments().collect();
         let props_name = &self.props_name;
         let comp_name = &self.comp_name;
 
@@ -99,7 +110,7 @@ impl Route {
         }
     }
 
-    pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
+    fn dynamic_segments(&self)-> impl Iterator<Item = TokenStream2> +'_{
         let segments = self.route_segments.iter().filter_map(|seg| {
             seg.name().map(|name| {
                 quote! {
@@ -107,6 +118,18 @@ impl Route {
                 }
             })
         });
+        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 name = &self.route_name;
 
         quote! {
@@ -165,13 +188,20 @@ impl Route {
             }
         }
     }
+
+    pub fn parse_query(&self) -> TokenStream2 {
+        match &self.query {
+            Some(query) => query.parse(),
+            None => quote! {},
+        }
+    }
 }
 
 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);
@@ -199,10 +229,17 @@ impl ToTokens for Route {
     }
 }
 
-fn parse_route_segments(varient: &Variant, route: &LitStr) -> syn::Result<Vec<RouteSegment>> {
+fn parse_route_segments(
+    varient: &Variant,
+    route: &LitStr,
+) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
     let mut route_segments = Vec::new();
 
     let route_string = route.value();
+    let (route_string, query) = match route_string.rsplit_once('?') {
+        Some((route, query)) => (route, Some(query)),
+        None => (route_string.as_str(), None),
+    };
     let mut iterator = route_string.split('/');
 
     // skip the first empty segment
@@ -268,7 +305,40 @@ fn parse_route_segments(varient: &Variant, route: &LitStr) -> syn::Result<Vec<Ro
         }
     }
 
-    Ok(route_segments)
+    // check if the route has a query string
+    let parsed_query = match query {
+        Some(query) => {
+            if query.starts_with('(') && query.ends_with(')') {
+                let query_ident = Ident::new(&query[1..query.len() - 1], Span::call_site());
+                let field = varient.fields.iter().find(|field| match field.ident {
+                    Some(ref field_ident) => field_ident == &query_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 '{}'",
+                            query_ident, varient.ident
+                        ),
+                    ));
+                };
+
+                Some(QuerySegment {
+                    ident: query_ident,
+                    ty,
+                })
+            } else {
+                None
+            }
+        }
+        None => None,
+    };
+
+    Ok((route_segments, parsed_query))
 }
 
 #[derive(Debug)]
@@ -323,11 +393,13 @@ impl RouteSegment {
             }
             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)));
+                    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)));
                 }
             }
-            Self::CatchAll(_, _) => {
-                todo!()
+            Self::CatchAll(_, 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)));
+                }
             }
         }
     }
@@ -336,3 +408,30 @@ impl RouteSegment {
 pub fn static_segment_idx(idx: usize) -> Ident {
     format_ident!("StaticSegment{}ParseError", idx)
 }
+
+#[derive(Debug)]
+pub struct QuerySegment {
+    ident: Ident,
+    ty: Type,
+}
+
+impl QuerySegment {
+    pub fn parse(&self) -> TokenStream2 {
+        let ident = &self.ident;
+        let ty = &self.ty;
+        quote! {
+            let #ident = <#ty as dioxus_router_core::router::FromQuery>::from_query(query);
+        }
+    }
+
+    pub fn write(&self) -> TokenStream2 {
+        let ident = &self.ident;
+        quote! {
+            write!(f, "?{}", #ident)?;
+        }
+    }
+
+    pub fn name(&self) -> Ident {
+        self.ident.clone()
+    }
+}

+ 6 - 0
packages/router-macro/src/route_tree.rs

@@ -175,6 +175,7 @@ impl<'a> RouteTreeSegment<'a> {
                 }
 
                 let construct_variant = route.construct(enum_name);
+                let parse_query = route.parse_query();
 
                 print_route_segment(
                     route_segments.peekable(),
@@ -183,6 +184,7 @@ impl<'a> RouteTreeSegment<'a> {
                         &error_enum_name,
                         enum_varient,
                         &varient_parse_error,
+                        parse_query,
                     ),
                 )
             }
@@ -190,12 +192,14 @@ impl<'a> RouteTreeSegment<'a> {
                 let varient_parse_error = route.error_ident();
                 let enum_varient = &route.route_name;
                 let construct_variant = route.construct(enum_name);
+                let parse_query = route.parse_query();
 
                 return_constructed(
                     construct_variant,
                     &error_enum_name,
                     enum_varient,
                     &varient_parse_error,
+                    parse_query,
                 )
             }
         }
@@ -207,6 +211,7 @@ fn return_constructed(
     error_enum_name: &Ident,
     enum_varient: &Ident,
     varient_parse_error: &Ident,
+    parse_query: TokenStream,
 ) -> TokenStream {
     quote! {
         let remaining_segments = segments.clone();
@@ -216,6 +221,7 @@ fn return_constructed(
         match (next_segment, segment_after_next) {
             // This is the last segment, return the parsed route
             (None, _) | (Some(""), None) => {
+                #parse_query
                 return Ok(#construct_variant);
             }
             _ => {

+ 0 - 114
packages/router/src/components/outlet.rs

@@ -1,114 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_router_core::{Name, OutletData};
-use log::error;
-
-use crate::utils::use_router_internal::use_router_internal;
-
-/// The properties for an [`Outlet`].
-#[derive(Debug, Eq, PartialEq, Props)]
-pub struct OutletProps {
-    /// Override the [`Outlet`]s nesting depth.
-    ///
-    /// By default the [`Outlet`] will find its own depth. This property overrides that depth.
-    /// Nested [`Outlet`]s will respect this override and calculate their depth based on it.
-    pub depth: Option<usize>,
-    /// The content name.
-    ///
-    /// By default, the outlet will render unnamed content. If this is set to a name, the outlet
-    /// will render content for that name, defined via [`RouteContent::MultiContent`].
-    ///
-    /// [`RouteContent::MultiContent`]: dioxus_router_core::routes::RouteContent::MultiContent
-    pub name: Option<Name>,
-}
-
-/// An outlet for the current content.
-///
-/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
-///
-/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content
-/// of the active route that is __exactly as deep__.
-///
-/// [`use_router`]: crate::hooks::use_router
-///
-/// # Panic
-/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
-///   but only in debug builds.
-///
-/// # Example
-/// ```rust
-/// # use dioxus::prelude::*;
-/// # use dioxus_router::prelude::*;
-/// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             ..Default::default()
-///         },
-///         &|| Segment::content(comp(Content))
-///     );
-///
-///     render! {
-///         h1 { "App" }
-///         Outlet { } // The content component will be rendered here
-///     }
-/// }
-///
-/// fn Content(cx: Scope) -> Element {
-///     render! {
-///         p { "Content" }
-///     }
-/// }
-/// #
-/// # let mut vdom = VirtualDom::new(App);
-/// # let _ = vdom.rebuild();
-/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Content</p>");
-/// ```
-#[allow(non_snake_case)]
-pub fn Outlet(cx: Scope<OutletProps>) -> Element {
-    let OutletProps { depth, name } = cx.props;
-
-    // hook up to router
-    let router = match use_router_internal(cx) {
-        Some(r) => r,
-        #[allow(unreachable_code)]
-        None => {
-            let msg = "`Outlet` must have access to a parent router";
-            error!("{msg}, will be inactive");
-            #[cfg(debug_assertions)]
-            panic!("{}", msg);
-            return None;
-        }
-    };
-    let state = loop {
-        if let Some(state) = router.state.try_read() {
-            break state;
-        }
-    };
-
-    // do depth calculation and propagation
-    let depth = cx.use_hook(|| {
-        let mut context = cx.consume_context::<OutletData>().unwrap_or_default();
-        let depth = depth
-            .or_else(|| context.depth(name).map(|d| d + 1))
-            .unwrap_or_default();
-        context.set_depth(name, depth);
-        cx.provide_context(context);
-        depth
-    });
-
-    // get content
-    let content = match name {
-        None => state.content.get(*depth),
-        Some(n) => state.named_content.get(n).and_then(|n| n.get(*depth)),
-    }
-    .cloned();
-
-    cx.render(match content {
-        Some(content) => {
-            let X = content.0;
-            rsx!(X {})
-        }
-        None => rsx!(()),
-    })
-}