From a509a7336ab4438e20efae0d9f08732e0bcbaa2c Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 04:53:57 -0500 Subject: [PATCH 01/10] Add a feature gate for service --- Cargo.toml | 5 +++-- .../yew_router_macro/src/switch/enum_impl.rs | 2 +- .../src/switch/struct_impl.rs | 2 +- examples/minimal/Cargo.toml | 2 +- examples/switch/Cargo.toml | 2 +- src/lib.rs | 19 ++++++++++++++----- src/route.rs | 13 +++++++++++-- src/switch.rs | 14 +++++++------- 8 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45e1865..2cd1956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ unit_alias = [] router = ["agent"] components = ["agent" ] -agent = [] +agent = ["service"] +service = ["stdweb"] [dependencies] @@ -28,7 +29,7 @@ serde = "1.0.103" serde_derive = "1.0.103" #yew = "0.10.0" yew = {git = "https://github.com/yewstack/yew", branch = "master"} -stdweb = "0.4.20" +stdweb = {version = "0.4.20", optional = true} yew-router-route-parser = {path = "crates/yew_router_route_parser", version = "0.8.0"} yew-router-macro = {path = "crates/yew_router_macro", version = "0.8.0"} diff --git a/crates/yew_router_macro/src/switch/enum_impl.rs b/crates/yew_router_macro/src/switch/enum_impl.rs index e5e869e..818d065 100644 --- a/crates/yew_router_macro/src/switch/enum_impl.rs +++ b/crates/yew_router_macro/src/switch/enum_impl.rs @@ -33,7 +33,7 @@ pub fn generate_enum_impl( let token_stream = quote! { #impl_line { - fn from_route_part<__T: ::yew_router::route::RouteState>(route: ::yew_router::route::Route<__T>) -> (::std::option::Option, ::std::option::Option<__T>) { + fn from_route_part<__T>(route: ::yew_router::route::Route<__T>) -> (::std::option::Option, ::std::option::Option<__T>) { let mut state = route.state; let route_string = route.route; #(#variant_matchers)* diff --git a/crates/yew_router_macro/src/switch/struct_impl.rs b/crates/yew_router_macro/src/switch/struct_impl.rs index a1c553b..0d0f5ea 100644 --- a/crates/yew_router_macro/src/switch/struct_impl.rs +++ b/crates/yew_router_macro/src/switch/struct_impl.rs @@ -24,7 +24,7 @@ pub fn generate_struct_impl(item: SwitchItem, generics: Generics) -> TokenStream let token_stream = quote! { #impl_line { - fn from_route_part<__T: ::yew_router::route::RouteState>(route: ::yew_router::route::Route<__T>) -> (::std::option::Option, ::std::option::Option<__T>) { + fn from_route_part<__T>(route: ::yew_router::route::Route<__T>) -> (::std::option::Option, ::std::option::Option<__T>) { #matcher let mut state = route.state; diff --git a/examples/minimal/Cargo.toml b/examples/minimal/Cargo.toml index d6ccf9f..9202fb9 100644 --- a/examples/minimal/Cargo.toml +++ b/examples/minimal/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] #yew = "0.10.0" yew = {git = "https://github.com/yewstack/yew", branch = "master"} -yew-router = {path = "../../", default-features=false} +yew-router = {path = "../../", default-features=false, features = ["service"]} web_logger = "0.1" log = "0.4.8" wee_alloc = "0.4.5" diff --git a/examples/switch/Cargo.toml b/examples/switch/Cargo.toml index 4aa31ca..3612895 100644 --- a/examples/switch/Cargo.toml +++ b/examples/switch/Cargo.toml @@ -7,4 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -yew-router = {path = "../../"} +yew-router = {path = "../../", default-features=false} diff --git a/src/lib.rs b/src/lib.rs index 092fd31..977411b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,8 @@ pub use yew_router_route_parser; #[macro_use] mod alias; + +#[cfg(feature = "service")] pub mod service; #[cfg(feature = "agent")] @@ -83,26 +85,32 @@ pub mod unit_state { pub mod prelude { pub use super::matcher::Captures; + #[cfg(feature = "service")] + pub use crate::service::RouteService; + #[cfg(feature = "service")] + pub use crate::route::RouteState; + #[cfg(feature = "agent")] pub use crate::agent::RouteAgent; #[cfg(feature = "agent")] pub use crate::agent::RouteAgentBridge; #[cfg(feature = "agent")] pub use crate::agent::RouteAgentDispatcher; + #[cfg(feature = "components")] pub use crate::components::RouterAnchor; #[cfg(feature = "components")] pub use crate::components::RouterButton; + #[cfg(feature = "router")] pub use crate::router::Router; - pub use crate::{route::Route, service::RouteService}; - pub use crate::switch::{Switch, Routable}; - pub use yew_router_macro::Switch; - // State restrictions - pub use crate::route::RouteState; #[cfg(feature = "router")] pub use crate::router::RouterState; + + pub use crate::route::Route; + pub use crate::switch::{Switch, Routable}; + pub use yew_router_macro::Switch; } pub use alias::*; @@ -111,6 +119,7 @@ pub mod matcher; pub use matcher::Captures; +#[cfg(feature = "service")] pub use crate::route::RouteState; #[cfg(feature = "router")] pub use crate::router::RouterState; diff --git a/src/route.rs b/src/route.rs index 5859df2..e2f8cc0 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,13 +1,19 @@ //! Wrapper around route url string, and associated history state. +#[cfg(feature = "service")] use crate::service::RouteService; +#[cfg(feature = "service")] +use stdweb::{unstable::TryFrom, Value}; +#[cfg(feature = "service")] +use serde::de::DeserializeOwned; + use serde::{Deserialize, Serialize}; use std::{fmt, ops::Deref}; -use stdweb::{unstable::TryFrom, Value}; use std::fmt::Debug; -use serde::de::DeserializeOwned; /// Any state that can be used in the router agent must meet the criteria of this trait. +#[cfg(feature = "service")] pub trait RouteState: Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom + 'static {} +#[cfg(feature = "service")] impl RouteState for T where T: Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom + 'static {} /// The representation of a route, segmented into different sections for easy access. @@ -19,6 +25,8 @@ pub struct Route { pub state: Option, } + +#[cfg(feature = "service")] /// Formats a path, query, and fragment into a string. /// /// # Note @@ -32,6 +40,7 @@ pub(crate) fn format_route_string(path: &str, query: &str, fragment: &str) -> St ) } +#[cfg(feature = "service")] impl Route { /// Gets the current route from the route service. /// diff --git a/src/switch.rs b/src/switch.rs index 4ba72ec..7353a1e 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -1,5 +1,5 @@ //! Parses routes into enums or structs. -use crate::{route::Route, RouteState}; +use crate::{route::Route}; use std::fmt::Write; /// Alias to Switch. @@ -49,12 +49,12 @@ pub type Routable = Switch; /// ``` pub trait Switch: Sized { /// Based on a route, possibly produce an itself. - fn switch(route: Route) -> Option { + fn switch(route: Route) -> Option { Self::from_route_part(route).0 } /// Get self from a part of the state - fn from_route_part(part: Route) -> (Option, Option); + fn from_route_part(part: Route) -> (Option, Option); /// Build part of a route from itself. fn build_route_section(self, route: &mut String) -> Option; @@ -81,7 +81,7 @@ pub trait Switch: Sized { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct LeadingSlash(pub T); impl Switch for LeadingSlash { - fn from_route_part(part: Route) -> (Option, Option) { + fn from_route_part(part: Route) -> (Option, Option) { if part.route.starts_with('/') { let route = Route { route: part.route[1..].to_string(), @@ -102,7 +102,7 @@ impl Switch for LeadingSlash { impl Switch for Option { /// Option is very permissive in what is allowed. - fn from_route_part(part: Route) -> (Option, Option) { + fn from_route_part(part: Route) -> (Option, Option) { let (inner, inner_state) = U::from_route_part(part); if inner.is_some() { (Some(inner), inner_state) @@ -130,7 +130,7 @@ impl Switch for Option { #[derive(Debug, PartialEq, Clone, Copy)] pub struct AllowMissing(pub Option); impl Switch for AllowMissing { - fn from_route_part(part: Route) -> (Option, Option) { + fn from_route_part(part: Route) -> (Option, Option) { let route = part.route.clone(); let (inner, inner_state) = U::from_route_part(part); @@ -161,7 +161,7 @@ macro_rules! impl_switch_for_from_to_str { ($($SelfT: ty),*) => { $( impl Switch for $SelfT { - fn from_route_part(part: Route) -> (Option, Option) { + fn from_route_part(part: Route) -> (Option, Option) { ( ::std::str::FromStr::from_str(&part.route).ok(), part.state From a5c5be80461bd8936d695a46a59c0f21b2bfe6e0 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 05:05:42 -0500 Subject: [PATCH 02/10] fix minimal readme --- examples/minimal/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/minimal/README.md b/examples/minimal/README.md index 85c4aec..4ff2bef 100644 --- a/examples/minimal/README.md +++ b/examples/minimal/README.md @@ -1,7 +1,7 @@ # Minimal Example -This example shows how to use this library without any of the features turned on. -Without any features, you lack the `Router` component and `RouteAgent` and its associated bridges and dispatchers. +This example shows how to use this library with only the "service" feature turned on. +Without most of the features, you lack the `Router` component and `RouteAgent` and its associated bridges and dispatchers. This means that you must use the `RouteService` to interface with the browser to handle route changes. Removing the `Router` component means that you have to deal with the `RouteService` directly and propagate change route messages up to the component that contains the `RouteService`. From e00fe0c905259ec2ee56809ea72a59121c34973f Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 05:06:06 -0500 Subject: [PATCH 03/10] remove yew as a dependency when compiling with no features --- Cargo.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2cd1956..3b72ee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,15 +20,14 @@ unit_alias = [] router = ["agent"] components = ["agent" ] agent = ["service"] -service = ["stdweb"] +service = ["stdweb", "yew"] [dependencies] log = "0.4.8" -serde = "1.0.103" -serde_derive = "1.0.103" +serde = {version = "1.0.103", features = ["derive"]} #yew = "0.10.0" -yew = {git = "https://github.com/yewstack/yew", branch = "master"} +yew = {git = "https://github.com/yewstack/yew", branch = "master", optional = true} stdweb = {version = "0.4.20", optional = true} yew-router-route-parser = {path = "crates/yew_router_route_parser", version = "0.8.0"} From 2d634cfadd812b6766a354a88b0de111f1a96fd1 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 05:10:30 -0500 Subject: [PATCH 04/10] add todo to remove unit_state --- Cargo.toml | 12 +++++++----- src/lib.rs | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b72ee7..b670131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,15 @@ repository = "https://github.com/yewstack/yew_router" [features] default = ["core", "unit_alias"] -core = ["router", "components"] +core = ["router", "components"] # Most everything + +# TODO remove this unit_alias = [] -router = ["agent"] -components = ["agent" ] -agent = ["service"] -service = ["stdweb", "yew"] +router = ["agent"] # The Router component +components = ["agent" ] # The button and anchor +agent = ["service"] # The RouteAgent +service = ["stdweb", "yew"] # The RouteService [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 977411b..9eb71d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,7 @@ pub mod components; #[cfg(feature = "router")] pub mod router; +/// TODO remove this /// Contains aliases and functions for working with this library using a state of type `()`. #[cfg(feature = "unit_alias")] pub mod unit_state { From 86f4877401866f25b759d91d44da01f16f288bf7 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 09:38:49 -0500 Subject: [PATCH 05/10] implement Switch generically --- examples/switch/src/main.rs | 25 ++++++------ src/route.rs | 23 ++++++----- src/switch.rs | 77 ++++++++++++------------------------- 3 files changed, 50 insertions(+), 75 deletions(-) diff --git a/examples/switch/src/main.rs b/examples/switch/src/main.rs index 7e4223f..04607ca 100644 --- a/examples/switch/src/main.rs +++ b/examples/switch/src/main.rs @@ -1,35 +1,36 @@ use yew_router::{route::Route, Switch}; +use yew_router::switch::Permissive; fn main() { - let route = Route::<()>::from("/some/route"); + let route = Route::new_no_state("/some/route"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/some/thing/other"); + let route = Route::new_no_state("/some/thing/other"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/another/other"); + let route = Route::new_no_state("/another/other"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/inner/left"); + let route = Route::new_no_state("/inner/left"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/yeet"); // should not match + let route = Route::new_no_state("/yeet"); // should not match let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/single/32"); + let route = Route::new_no_state("/single/32"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/othersingle/472"); + let route = Route::new_no_state("/othersingle/472"); let app_route = AppRoute::switch(route); dbg!(app_route); - let route = Route::<()>::from("/option/test"); + let route = Route::new_no_state("/option/test"); let app_route = AppRoute::switch(route); dbg!(app_route); @@ -69,12 +70,12 @@ pub enum AppRoute { Single(Single), #[rest] OtherSingle(OtherSingle), - /// Because this is an option, the inner item doesn't have to match. + /// Because this is permissive, the inner item doesn't have to match. #[to = "/option/{}"] - Optional(Option), - /// Because this is an option, a corresponding capture group doesn't need to exist + Optional(Permissive), + /// Because this is permissive, a corresponding capture group doesn't need to exist #[to = "/missing/capture"] - MissingCapture(Option), + MissingCapture(Permissive), } #[derive(Switch, Debug, Clone)] diff --git a/src/route.rs b/src/route.rs index e2f8cc0..3c26e2b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -22,7 +22,7 @@ pub struct Route { /// The route string pub route: String, /// The state stored in the history api - pub state: Option, + pub state: Option, // TODO eventually make this just `T` } @@ -57,21 +57,24 @@ impl Route { } } -impl fmt::Display for Route { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - std::fmt::Display::fmt(&self.route, f) - } -} - -impl From<&str> for Route { - fn from(string: &str) -> Route { +impl Route<()> { + /// Creates a new route out of a string. + /// + /// This Route will have `()` for its state. + pub fn new_no_state>(route: T) -> Self { Route { - route: string.to_string(), + route: route.as_ref().to_string(), state: None, } } } +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(&self.route, f) + } +} + impl Deref for Route { type Target = String; diff --git a/src/switch.rs b/src/switch.rs index 7353a1e..16f2ae7 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -100,20 +100,24 @@ impl Switch for LeadingSlash { } } -impl Switch for Option { +/// Successfully match even when the captured section can't be found. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Permissive(pub Option); + +impl Switch for Permissive { /// Option is very permissive in what is allowed. fn from_route_part(part: Route) -> (Option, Option) { let (inner, inner_state) = U::from_route_part(part); if inner.is_some() { - (Some(inner), inner_state) + (Some(Permissive(inner)), inner_state) } else { // The Some(None) here indicates that this will produce a None, if the wrapped value can't be parsed - (Some(None), None) + (Some(Permissive(None)), None) } } fn build_route_section(self, route: &mut String) -> Option { - if let Some(inner) = self { + if let Some(inner) = self.0 { inner.build_route_section(route) } else { None @@ -121,14 +125,17 @@ impl Switch for Option { } fn key_not_available() -> Option { - Some(None) + Some(Permissive(None)) } } +// TODO the AllowMissing shim doesn't appear to offer much over Permissive. +// Documentation should improve (need examples - to show the difference) or it should be removed. + /// Allows a section to match, providing a None value, /// if its contents are entirely missing, or starts with a '/'. #[derive(Debug, PartialEq, Clone, Copy)] -pub struct AllowMissing(pub Option); +pub struct AllowMissing(pub Option); impl Switch for AllowMissing { fn from_route_part(part: Route) -> (Option, Option) { let route = part.route.clone(); @@ -157,54 +164,18 @@ impl Switch for AllowMissing { } } -macro_rules! impl_switch_for_from_to_str { - ($($SelfT: ty),*) => { - $( - impl Switch for $SelfT { - fn from_route_part(part: Route) -> (Option, Option) { - ( - ::std::str::FromStr::from_str(&part.route).ok(), - part.state - ) - } - - fn build_route_section(self, f: &mut String) -> Option { - write!(f, "{}", self).expect("Writing to string should never fail."); - None - } - } - )* - }; -} +impl Switch for T { + fn from_route_part(part: Route) -> (Option, Option) { + ( + ::std::str::FromStr::from_str(&part.route).ok(), + part.state + ) + } -impl_switch_for_from_to_str! { - String, - uuid::Uuid, - bool, - f64, - f32, - usize, - u128, - u64, - u32, - u16, - u8, - isize, - i128, - i64, - i32, - i16, - i8, - std::num::NonZeroU128, - std::num::NonZeroU64, - std::num::NonZeroU32, - std::num::NonZeroU16, - std::num::NonZeroU8, - std::num::NonZeroI128, - std::num::NonZeroI64, - std::num::NonZeroI32, - std::num::NonZeroI16, - std::num::NonZeroI8 + fn build_route_section(self, route: &mut String) -> Option { + write!(route, "{}", self).expect("Writing to string should never fail."); + None + } } /// Builds a route from a switch. From 0848bc1f7a17ede1810ac966e06361d2e75f1be8 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 09:55:44 -0500 Subject: [PATCH 06/10] fix permissive in router_component example --- examples/router_component/src/main.rs | 10 +++++----- src/switch.rs | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/router_component/src/main.rs b/examples/router_component/src/main.rs index 7968c5b..9ba4d69 100644 --- a/examples/router_component/src/main.rs +++ b/examples/router_component/src/main.rs @@ -13,7 +13,7 @@ use crate::{ c_component::CModel, }; use yew::virtual_dom::VNode; -use yew_router::switch::AllowMissing; +use yew_router::switch::{AllowMissing, Permissive}; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; @@ -62,12 +62,12 @@ impl Component for Model { }, AppRoute::C => html!{}, AppRoute::E(string) => html!{format!("hello {}", string)}, - AppRoute::PageNotFound(None) => html!{"Page not found"}, - AppRoute::PageNotFound(Some(missed_route)) => html!{format!("Page '{}' not found", missed_route)} + AppRoute::PageNotFound(Permissive(None)) => html!{"Page not found"}, + AppRoute::PageNotFound(Permissive(Some(missed_route))) => html!{format!("Page '{}' not found", missed_route)} } }) redirect = Router::redirect(|route: Route| { - AppRoute::PageNotFound(Some(route.route)) + AppRoute::PageNotFound(Permissive(Some(route.route))) }) /> @@ -87,7 +87,7 @@ pub enum AppRoute { #[to = "/e/{string}"] E(String), #[to = "/page-not-found"] - PageNotFound(Option), + PageNotFound(Permissive), } #[derive(Debug, Switch, PartialEq, Clone, Copy)] diff --git a/src/switch.rs b/src/switch.rs index 16f2ae7..7e1ba2d 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -164,6 +164,7 @@ impl Switch for AllowMissing { } } +// TODO explore if adding a crate-defined trait here would satisfy coherence rules for option. Then add that trait to all items previously in the macro. impl Switch for T { fn from_route_part(part: Route) -> (Option, Option) { ( From 7e993f208f2bfced04cb9c355a6d42e52e6c9828 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 10:08:24 -0500 Subject: [PATCH 07/10] fix tests --- src/switch.rs | 12 +++--- tests/macro_test/src/lib.rs | 73 +++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/switch.rs b/src/switch.rs index 7e1ba2d..e803fba 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -29,21 +29,21 @@ pub type Routable = Switch; /// } /// /// assert_eq!( -/// TestEnum::switch(Route::<()>::from("/test/route")), +/// TestEnum::switch(Route::new_no_state("/test/route")), /// Some(TestEnum::TestRoute) /// ); /// assert_eq!( -/// TestEnum::switch(Route::<()>::from("/capture/string/lorem")), +/// TestEnum::switch(Route::new_no_state("/capture/string/lorem")), /// Some(TestEnum::CaptureString { /// path: "lorem".to_string() /// }) /// ); /// assert_eq!( -/// TestEnum::switch(Route::<()>::from("/capture/number/22")), +/// TestEnum::switch(Route::new_no_state("/capture/number/22")), /// Some(TestEnum::CaptureNumber { num: 22 }) /// ); /// assert_eq!( -/// TestEnum::switch(Route::<()>::from("/capture/unnamed/lorem")), +/// TestEnum::switch(Route::new_no_state("/capture/unnamed/lorem")), /// Some(TestEnum::CaptureUnnamed("lorem".to_string())) /// ); /// ``` @@ -238,10 +238,10 @@ mod test { #[test] fn can_get_option_string_from_empty_str() { - let (s, _state): (Option>, Option<()>) = Option::from_route_part(Route { + let (s, _state): (Option>, Option<()>) = Permissive::from_route_part(Route { route: "".to_string(), state: None, }); - assert_eq!(s, Some(Some("".to_string()))) + assert_eq!(s, Some(Permissive(Some("".to_string())))) } } diff --git a/tests/macro_test/src/lib.rs b/tests/macro_test/src/lib.rs index b345d57..9543636 100644 --- a/tests/macro_test/src/lib.rs +++ b/tests/macro_test/src/lib.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests { use yew_router::{prelude::Route, Switch}; + use yew_router::switch::Permissive; #[test] fn single_enum_variant() { @@ -9,7 +10,7 @@ mod tests { #[to = "/variant"] Variant, } - let route: Route = Route::from("/variant"); + let route = Route::new_no_state("/variant"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant) } @@ -21,12 +22,12 @@ mod tests { #[to = "/variant"] Variant(String), } - let route: Route = Route::from("/variant"); + let route = Route::new_no_state("/variant"); assert!( Test::switch(route).is_none(), "there should not be a way to ever create this variant." ); - let route: Route = Route::from("/variant/some/stuff"); + let route = Route::new_no_state("/variant/some/stuff"); assert!( Test::switch(route).is_none(), "there should not be a way to ever create this variant." @@ -40,7 +41,7 @@ mod tests { #[to = "/variant/{item}"] Variant { item: String }, } - let route: Route = Route::from("/variant/thing"); + let route = Route::new_no_state("/variant/thing"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -57,7 +58,7 @@ mod tests { #[to = "/variant/{item}"] Variant(String), } - let route: Route = Route::from("/variant/thing"); + let route = Route::new_no_state("/variant/thing"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("thing".to_string())) } @@ -69,7 +70,7 @@ mod tests { #[to = "/variant/{}/{}"] // For unnamed variants, the names don't matter at all Variant(String, String), } - let route: Route = Route::from("/variant/thing/other"); + let route = Route::new_no_state("/variant/thing/other"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -84,7 +85,7 @@ mod tests { #[to = "/variant/{item1}/{item2}"] Variant { item1: String, item2: String }, } - let route: Route = Route::from("/variant/thing/other"); + let route = Route::new_no_state("/variant/thing/other"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -102,7 +103,7 @@ mod tests { #[to = "/variant{item}"] Variant { item: String }, } - let route: Route = Route::from("/variantthing"); + let route = Route::new_no_state("/variantthing"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -119,7 +120,7 @@ mod tests { #[to = "/variant{item}stuff"] Variant { item: String }, } - let route: Route = Route::from("/variantthingstuff"); + let route = Route::new_no_state("/variantthingstuff"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -136,7 +137,7 @@ mod tests { #[to = "/variant!"] Variant, } - let route: Route = Route::from("/variant/"); + let route = Route::new_no_state("/variant/"); assert!(Test::switch(route).is_none()); } @@ -149,7 +150,7 @@ mod tests { #[to = "/variant/stuff"] Variant2, } - let route: Route = Route::from("/variant/stuff"); + let route = Route::new_no_state("/variant/stuff"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -167,7 +168,7 @@ mod tests { #[to = "/variant/stuff"] Variant2, } - let route: Route = Route::from("/variant/stuff"); + let route = Route::new_no_state("/variant/stuff"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -183,7 +184,7 @@ mod tests { #[to = "/variant/{item}"] Variant(usize), } - let route: Route = Route::from("/variant/42"); + let route = Route::new_no_state("/variant/42"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant(42)) } @@ -195,7 +196,7 @@ mod tests { #[to = "/variant/{item}"] Variant(usize), } - let route: Route = Route::from("/variant/-42"); + let route = Route::new_no_state("/variant/-42"); assert!(Test::switch(route).is_none()); } @@ -206,21 +207,21 @@ mod tests { #[to = "/variant/{item}"] Variant(isize), } - let route: Route = Route::from("/variant/-42"); + let route = Route::new_no_state("/variant/-42"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant(-42)) } #[test] - fn single_enum_variant_missing_cap_produces_option_none() { + fn single_enum_variant_missing_cap_produces_permissive_option_none() { #[derive(Debug, Switch, PartialEq, Clone)] pub enum Test { #[to = "/variant"] - Variant(Option), + Variant(Permissive), } - let route: Route = Route::from("/variant"); + let route = Route::new_no_state("/variant"); let switched = Test::switch(route).expect("should produce item"); - assert_eq!(switched, Test::Variant(None)) + assert_eq!(switched, Test::Variant(Permissive(None))) } // TODO allow missing is a little broken at the moment. @@ -244,7 +245,7 @@ mod tests { #[to = "/"] Variant, } - let route: Route = Route::from("/"); + let route = Route::new_no_state("/"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant) } @@ -256,7 +257,7 @@ mod tests { #[to = "{cap}"] Variant(String), } - let route: Route = Route::from("hello"); + let route = Route::new_no_state("hello"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello".to_string())) } @@ -268,7 +269,7 @@ mod tests { #[to = "{}"] Variant(String), } - let route: Route = Route::from("hello"); + let route = Route::new_no_state("hello"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello".to_string())) } @@ -280,7 +281,7 @@ mod tests { #[to = "{2:cap}"] Variant(String), } - let route: Route = Route::from("hello/there"); + let route = Route::new_no_state("hello/there"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello/there".to_string())) } @@ -292,7 +293,7 @@ mod tests { #[to = "{2}"] Variant(String), } - let route: Route = Route::from("hello/there"); + let route = Route::new_no_state("hello/there"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello/there".to_string())) } @@ -304,7 +305,7 @@ mod tests { #[to = "{*:cap}"] Variant(String), } - let route: Route = Route::from("hello/there"); + let route = Route::new_no_state("hello/there"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello/there".to_string())) } @@ -316,7 +317,7 @@ mod tests { #[to = "{*}"] Variant(String), } - let route: Route = Route::from("hello/there"); + let route = Route::new_no_state("hello/there"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("hello/there".to_string())) } @@ -328,7 +329,7 @@ mod tests { #[to = "?query={hello}"] Variant(String), } - let route: Route = Route::from("?query=lorem"); + let route = Route::new_no_state("?query=lorem"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("lorem".to_string())) } @@ -340,7 +341,7 @@ mod tests { #[to = "?query={}"] Variant(String), } - let route: Route = Route::from("?query=lorem"); + let route = Route::new_no_state("?query=lorem"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant("lorem".to_string())) } @@ -352,7 +353,7 @@ mod tests { #[to = "#fragment"] Variant, } - let route: Route = Route::from("#fragment"); + let route = Route::new_no_state("#fragment"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant) } @@ -364,7 +365,7 @@ mod tests { #[to = "#{cap}ipsum{cap}"] Variant(String, String), } - let route: Route = Route::from("#loremipsumdolor"); + let route = Route::new_no_state("#loremipsumdolor"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -379,7 +380,7 @@ mod tests { #[to = "#{}ipsum{}"] Variant(String, String), } - let route: Route = Route::from("#loremipsumdolor"); + let route = Route::new_no_state("#loremipsumdolor"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, @@ -394,7 +395,7 @@ mod tests { #[to = "/escape!!"] Variant, } - let route: Route = Route::from("/escape!"); + let route = Route::new_no_state("/escape!"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant) } @@ -406,7 +407,7 @@ mod tests { #[to = "/escape{{}}a"] Variant, } - let route: Route = Route::from("/escape{}a"); + let route = Route::new_no_state("/escape{}a"); let switched = Test::switch(route).expect("should produce item"); assert_eq!(switched, Test::Variant) } @@ -421,7 +422,7 @@ mod tests { #[to = "#/lorem"] Variant, } - let route: Route = Route::from("#/lorem"); + let route = Route::new_no_state("#/lorem"); Test::switch(route).expect("should produce item"); } @@ -432,7 +433,7 @@ mod tests { #[to = "#/lorem=ipsum"] Variant, } - let route: Route = Route::from("#/lorem=ipsum"); + let route = Route::new_no_state("#/lorem=ipsum"); Test::switch(route).expect("should produce item"); } @@ -443,7 +444,7 @@ mod tests { #[to = "#/lorem={ipsum}"] Variant { ipsum: String }, } - let route: Route = Route::from("#/lorem=dolor"); + let route = Route::new_no_state("#/lorem=dolor"); let switched = Test::switch(route).expect("should produce item"); assert_eq!( switched, From 9a14013a744c27e09ca7f206a7890ecb9ff76fd3 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 10:46:07 -0500 Subject: [PATCH 08/10] add method for creating a route with its default state --- src/route.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/route.rs b/src/route.rs index 3c26e2b..f72e6f0 100644 --- a/src/route.rs +++ b/src/route.rs @@ -58,7 +58,7 @@ impl Route { } impl Route<()> { - /// Creates a new route out of a string. + /// Creates a new route with no state out of a string. /// /// This Route will have `()` for its state. pub fn new_no_state>(route: T) -> Self { @@ -69,6 +69,16 @@ impl Route<()> { } } +impl Route { + /// Creates a new route out of a string, setting the state to its default value. + pub fn new_default_state>(route: U) -> Self { + Route { + route: route.as_ref().to_string(), + state: Some(T::default()), + } + } +} + impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { std::fmt::Display::fmt(&self.route, f) From d0df1ff90f1f0c93f71cf2fffbcc75451a760d58 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 16:11:37 -0500 Subject: [PATCH 09/10] other merged --- src/route.rs | 22 +++++++++++----------- src/switch.rs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/route.rs b/src/route.rs index 9a51c19..56a90a7 100644 --- a/src/route.rs +++ b/src/route.rs @@ -47,7 +47,7 @@ impl Route<()> { pub fn new_no_state>(route: T) -> Self { Route { route: route.as_ref().to_string(), - state: None, + state: (), } } } @@ -57,7 +57,7 @@ impl Route { pub fn new_default_state>(route: U) -> Self { Route { route: route.as_ref().to_string(), - state: Some(T::default()), + state: T::default(), } } } @@ -68,15 +68,15 @@ impl fmt::Display for Route { } } -// This is getting removed anyway -impl From<&str> for Route { - fn from(string: &str) -> Route { - Route { - route: string.to_string(), - state: T::default(), - } - } -} +//// This is getting removed anyway +//impl From<&str> for Route { +// fn from(string: &str) -> Route { +// Route { +// route: string.to_string(), +// state: T::default(), +// } +// } +//} impl Deref for Route { type Target = String; diff --git a/src/switch.rs b/src/switch.rs index 557eb8f..736a649 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -180,6 +180,21 @@ impl From for Route { } } + +impl Switch for T { + fn from_route_part(part: String, state: Option) -> (Option, Option) { + ( + ::std::str::FromStr::from_str(&part).ok(), + state + ) + } + + fn build_route_section(self, route: &mut String) -> Option { + write!(route, "{}", self).expect("Writing to string should never fail."); + None + } +} + #[cfg(test)] mod test { use super::*; @@ -224,6 +239,6 @@ mod test { "".to_string(), Some(()), ); - assert_eq!(s, Some(Some("".to_string()))) + assert_eq!(s, Some(Permissive(Some("".to_string())))) } } From d171cd72fd14e5cb3e9e540ab455be91c383bb36 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Tue, 17 Dec 2019 16:18:17 -0500 Subject: [PATCH 10/10] cleanup dead code, move formatting route code to service --- src/route.rs | 26 -------------------------- src/service.rs | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/route.rs b/src/route.rs index 56a90a7..b80affc 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,7 +1,5 @@ //! Wrapper around route url string, and associated history state. #[cfg(feature = "service")] -use crate::service::RouteService; -#[cfg(feature = "service")] use stdweb::{unstable::TryFrom, Value}; #[cfg(feature = "service")] use serde::de::DeserializeOwned; @@ -26,20 +24,6 @@ pub struct Route { } -#[cfg(feature = "service")] -/// Formats a path, query, and fragment into a string. -/// -/// # Note -/// This expects that all three already have their expected separators (?, #, etc) -pub(crate) fn format_route_string(path: &str, query: &str, fragment: &str) -> String { - format!( - "{path}{query}{fragment}", - path = path, - query = query, - fragment = fragment - ) -} - impl Route<()> { /// Creates a new route with no state out of a string. /// @@ -68,16 +52,6 @@ impl fmt::Display for Route { } } -//// This is getting removed anyway -//impl From<&str> for Route { -// fn from(string: &str) -> Route { -// Route { -// route: string.to_string(), -// state: T::default(), -// } -// } -//} - impl Deref for Route { type Target = String; diff --git a/src/service.rs b/src/service.rs index 77f4751..f80ae9b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -49,7 +49,7 @@ impl RouteService { let path = location.pathname().unwrap(); let query = location.search().unwrap(); let fragment = location.hash().unwrap(); - crate::route::format_route_string(&path, &query, &fragment) + format_route_string(&path, &query, &fragment) } @@ -144,6 +144,21 @@ where } } + +/// Formats a path, query, and fragment into a string. +/// +/// # Note +/// This expects that all three already have their expected separators (?, #, etc) +pub(crate) fn format_route_string(path: &str, query: &str, fragment: &str) -> String { + format!( + "{path}{query}{fragment}", + path = path, + query = query, + fragment = fragment + ) +} + + fn get_state(history: &History) -> Value { js!( return @{history}.state;