From 20ef3be4943d725f0b6a32ab0637e8832dc6e601 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Mon, 16 Dec 2019 16:50:05 -0500 Subject: [PATCH] Store state as a serialized string (#201) * remove AgentState * use a json string to store and restore state in the history api. * fix cargo format problem in warp server example --- Cargo.toml | 1 + examples/servers/warp/src/main.rs | 5 ++++- src/agent/bridge.rs | 15 ++++++-------- src/agent/dispatcher.rs | 15 +++++++------- src/agent/mod.rs | 10 ++++------ src/components/mod.rs | 2 +- src/components/router_button.rs | 4 ++-- src/components/router_link.rs | 4 ++-- src/lib.rs | 4 ---- src/route.rs | 12 ++++++----- src/router.rs | 33 +++++++++++++------------------ src/service.rs | 20 ++++++++++++++++--- 12 files changed, 66 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f610806..45e1865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ yew-router-route-parser = {path = "crates/yew_router_route_parser", version = "0 yew-router-macro = {path = "crates/yew_router_macro", version = "0.8.0"} nom = "5.0.1" uuid = "0.8.1" +serde_json = "1.0.44" diff --git a/examples/servers/warp/src/main.rs b/examples/servers/warp/src/main.rs index f2e95a3..385eed2 100644 --- a/examples/servers/warp/src/main.rs +++ b/examples/servers/warp/src/main.rs @@ -1,8 +1,11 @@ use std::path::PathBuf; + +#[rustfmt::skip] use warp::{ filters::BoxedFilter, fs::File, - path::{self, Peek}, + path::Peek, + path, Filter, Reply, }; diff --git a/src/agent/bridge.rs b/src/agent/bridge.rs index eea8626..3d05825 100644 --- a/src/agent/bridge.rs +++ b/src/agent/bridge.rs @@ -1,8 +1,5 @@ //! Bridge to RouteAgent. -use crate::{ - agent::{AgentState, RouteAgent}, - route::Route, -}; +use crate::{agent::{RouteAgent}, route::Route, RouteState}; use std::{ fmt::{Debug, Error as FmtError, Formatter}, ops::{Deref, DerefMut}, @@ -17,11 +14,11 @@ use yew::{ /// A component that owns this can send and receive messages from the agent. pub struct RouteAgentBridge(Box>>) where - for<'de> T: AgentState<'de>; + T: RouteState; impl RouteAgentBridge where - for<'de> T: AgentState<'de>, + T: RouteState, { /// Creates a new bridge. pub fn new(callback: Callback>) -> Self { @@ -39,20 +36,20 @@ where } } -impl AgentState<'de>> Debug for RouteAgentBridge { +impl Debug for RouteAgentBridge { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { f.debug_tuple("RouteAgentBridge").finish() } } -impl AgentState<'de>> Deref for RouteAgentBridge { +impl Deref for RouteAgentBridge { type Target = Box>>; fn deref(&self) -> &Self::Target { &self.0 } } -impl AgentState<'de>> DerefMut for RouteAgentBridge { +impl DerefMut for RouteAgentBridge { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } diff --git a/src/agent/dispatcher.rs b/src/agent/dispatcher.rs index dc6122f..b1fd0f9 100644 --- a/src/agent/dispatcher.rs +++ b/src/agent/dispatcher.rs @@ -1,21 +1,22 @@ //! Dispatcher to RouteAgent. -use crate::agent::{AgentState, RouteAgent}; +use crate::agent::{RouteAgent}; use std::{ fmt::{Debug, Error as FmtError, Formatter}, ops::{Deref, DerefMut}, }; use yew::agent::{Dispatched, Dispatcher}; +use crate::RouteState; /// A wrapped dispatcher to the route agent. /// /// A component that owns and instance of this can send messages to the RouteAgent, but not receive them. pub struct RouteAgentDispatcher(Dispatcher>) where - for<'de> T: AgentState<'de>; + T: RouteState; impl RouteAgentDispatcher where - for<'de> T: AgentState<'de>, + T: RouteState { /// Creates a new bridge. pub fn new() -> Self { @@ -26,27 +27,27 @@ where impl Default for RouteAgentDispatcher where - for<'de> T: AgentState<'de>, + T: RouteState { fn default() -> Self { Self::new() } } -impl AgentState<'de>> Debug for RouteAgentDispatcher { +impl Debug for RouteAgentDispatcher { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { f.debug_tuple("RouteAgentDispatcher").finish() } } -impl AgentState<'de>> Deref for RouteAgentDispatcher { +impl Deref for RouteAgentDispatcher { type Target = Dispatcher>; fn deref(&self) -> &Self::Target { &self.0 } } -impl AgentState<'de>> DerefMut for RouteAgentDispatcher { +impl DerefMut for RouteAgentDispatcher { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } diff --git a/src/agent/mod.rs b/src/agent/mod.rs index 4c24a4e..f1011e4 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -20,9 +20,7 @@ pub use bridge::RouteAgentBridge; mod dispatcher; pub use dispatcher::RouteAgentDispatcher; -/// Any state that can be used in the router agent must meet the criteria of this trait. -pub trait AgentState<'de>: RouteState + Serialize + Deserialize<'de> + Debug {} -impl<'de, T> AgentState<'de> for T where T: RouteState + Serialize + Deserialize<'de> + Debug {} + /// Internal Message used for the RouteAgent. #[derive(Debug)] @@ -61,7 +59,7 @@ pub enum RouteRequest { /// each other and associated components may not work as intended. pub struct RouteAgent where - for<'de> T: AgentState<'de>, + T: RouteState, { // In order to have the AgentLink below, apparently T must be constrained like this. // Unfortunately, this means that everything related to an agent requires this constraint. @@ -74,7 +72,7 @@ where subscribers: HashSet, } -impl AgentState<'de>> Debug for RouteAgent { +impl Debug for RouteAgent { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { f.debug_struct("RouteAgent") .field("link", &"-") @@ -86,7 +84,7 @@ impl AgentState<'de>> Debug for RouteAgent { impl Agent for RouteAgent where - for<'de> T: AgentState<'de>, + T: RouteState, { type Input = RouteRequest; type Message = Msg; diff --git a/src/components/mod.rs b/src/components/mod.rs index 9a91574..518229a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -15,7 +15,7 @@ use crate::RouterState; // TODO This should also be PartialEq and Clone. Its blocked on Children not supporting that. /// Properties for `RouterButton` and `RouterLink`. #[derive(Properties, Default, Debug)] -pub struct Props RouterState<'de>> { +pub struct Props { /// The route that will be set when the component is clicked. pub link: String, /// The state to set when changing the route. diff --git a/src/components/router_button.rs b/src/components/router_button.rs index 323494c..c2b9bca 100644 --- a/src/components/router_button.rs +++ b/src/components/router_button.rs @@ -11,13 +11,13 @@ use yew::virtual_dom::VNode; /// Changes the route when clicked. #[derive(Debug)] -pub struct RouterButton RouterState<'de> = ()> { +pub struct RouterButton { link: ComponentLink, router: RouteAgentDispatcher, props: Props, } -impl RouterState<'de>> Component for RouterButton { +impl Component for RouterButton { type Message = Msg; type Properties = Props; diff --git a/src/components/router_link.rs b/src/components/router_link.rs index 452a7a0..9a04a33 100644 --- a/src/components/router_link.rs +++ b/src/components/router_link.rs @@ -17,13 +17,13 @@ pub type RouterLink = RouterAnchor; /// An anchor tag Component that when clicked, will navigate to the provided route. #[derive(Debug)] -pub struct RouterAnchor RouterState<'de> = ()> { +pub struct RouterAnchor { link: ComponentLink, router: RouteAgentDispatcher, props: Props, } -impl RouterState<'de>> Component for RouterAnchor { +impl Component for RouterAnchor { type Message = Msg; type Properties = Props; diff --git a/src/lib.rs b/src/lib.rs index 400cf1e..84c58ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,8 +100,6 @@ pub mod prelude { pub use crate::switch::Switch; pub use yew_router_macro::Switch; // State restrictions - #[cfg(feature = "agent")] - pub use crate::agent::AgentState; pub use crate::route::RouteState; #[cfg(feature = "router")] pub use crate::router::RouterState; @@ -113,8 +111,6 @@ pub mod matcher; pub use matcher::Captures; -#[cfg(feature = "agent")] -pub use crate::agent::AgentState; pub use crate::route::RouteState; #[cfg(feature = "router")] pub use crate::router::RouterState; diff --git a/src/route.rs b/src/route.rs index 950dba9..5859df2 100644 --- a/src/route.rs +++ b/src/route.rs @@ -2,11 +2,13 @@ use crate::service::RouteService; use serde::{Deserialize, Serialize}; use std::{fmt, ops::Deref}; -use stdweb::{unstable::TryFrom, JsSerialize, Value}; +use stdweb::{unstable::TryFrom, Value}; +use std::fmt::Debug; +use serde::de::DeserializeOwned; -/// Any state that can be stored by the History API must meet the criteria of this trait. -pub trait RouteState: Clone + Default + JsSerialize + TryFrom + 'static {} -impl RouteState for T where T: Clone + Default + JsSerialize + TryFrom + 'static {} +/// Any state that can be used in the router agent must meet the criteria of this trait. +pub trait RouteState: Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom + 'static {} +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. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] @@ -48,7 +50,7 @@ impl Route { impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.route.fmt(f) + std::fmt::Display::fmt(&self.route, f) } } diff --git a/src/router.rs b/src/router.rs index daea4cf..66186a1 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,21 +1,16 @@ //! Router Component. -use crate::{ - agent::{RouteAgentBridge, RouteRequest}, - route::Route, - Switch, -}; +use crate::{agent::{RouteAgentBridge, RouteRequest}, route::Route, Switch, RouteState}; use std::{ fmt::{self, Debug, Error as FmtError, Formatter}, rc::Rc, }; use yew::{html, virtual_dom::VNode, Component, ComponentLink, Html, Properties, ShouldRender}; -use crate::agent::AgentState; /// Any state that can be managed by the `Router` must meet the criteria of this trait. -pub trait RouterState<'de>: AgentState<'de> + PartialEq {} -impl<'de, T> RouterState<'de> for T where T: AgentState<'de> + PartialEq {} +pub trait RouterState: RouteState + PartialEq {} +impl RouterState for T where T: RouteState + PartialEq {} /// Rendering control flow component. /// @@ -59,7 +54,7 @@ impl<'de, T> RouterState<'de> for T where T: AgentState<'de> + PartialEq {} /// ``` // TODO, can M just be removed due to not having to explicitly deal with callbacks anymore? - Just get rid of M #[derive(Debug)] -pub struct Router RouterState<'de> = ()> { +pub struct Router { switch: Option, props: Props, router_agent: RouteAgentBridge, @@ -67,7 +62,7 @@ pub struct Router RouterState<'de> = ( impl Router where - T: for<'de> RouterState<'de>, + T: RouterState, SW: Switch + Clone + 'static, { // TODO render fn name is overloaded now with that of the trait: Renderable<_> this should be changed. Maybe: display, show, switch, inner... @@ -114,16 +109,16 @@ pub trait RenderFn: Fn(SW) -> Html {} impl RenderFn for T where T: Fn(SW) -> Html {} /// Owned Render function. #[derive(Clone)] -pub struct Render RouterState<'de> = ()>( +pub struct Render( pub(crate) Rc, SW>>, ); -impl RouterState<'de>, SW: Switch + Clone> Render { +impl Render { /// New render function fn new, SW> + 'static>(f: F) -> Self { Render(Rc::new(f)) } } -impl RouterState<'de>, SW: Switch + Clone> Debug for Render { +impl Debug for Render { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Render").finish() } @@ -135,15 +130,15 @@ pub trait RedirectFn: Fn(Route) -> SW {} impl RedirectFn for T where T: Fn(Route) -> SW {} /// Clonable Redirect function #[derive(Clone)] -pub struct Redirect RouterState<'de>>( +pub struct Redirect( pub(crate) Rc>, ); -impl RouterState<'de>, SW: Switch + 'static> Redirect { +impl Redirect { fn new + 'static>(f: F) -> Self { Redirect(Rc::new(f)) } } -impl RouterState<'de>, SW: Switch> Debug for Redirect { +impl Debug for Redirect { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Redirect").finish() } @@ -151,7 +146,7 @@ impl RouterState<'de>, SW: Switch> Debug for Redirect /// Properties for Router. #[derive(Properties, Clone)] -pub struct Props RouterState<'de>, SW: Switch + Clone + 'static> { +pub struct Props { /// Render function that takes a Switch and produces Html #[props(required)] pub render: Render, @@ -161,7 +156,7 @@ pub struct Props RouterState<'de>, SW: Switch + Clone + 'static> { pub redirect: Option>, } -impl RouterState<'de>, SW: Switch + Clone> Debug for Props { +impl Debug for Props { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { f.debug_struct("Props").finish() } @@ -169,7 +164,7 @@ impl RouterState<'de>, SW: Switch + Clone> Debug for Props { impl Component for Router where - T: for<'de> RouterState<'de>, + T: RouterState, SW: Switch + Clone + 'static, { type Message = Msg; diff --git a/src/service.rs b/src/service.rs index 4a03c28..c6237f8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,6 +8,7 @@ use yew::callback::Callback; use crate::route::RouteState; use std::marker::PhantomData; +use stdweb::unstable::TryFrom; /// A service that facilitates manipulation of the browser's URL bar and responding to browser events /// when users press 'forward' or 'back'. @@ -83,7 +84,12 @@ where pub fn register_callback(&mut self, callback: Callback<(String, T)>) { self.event_listener = Some(window().add_event_listener(move |event: PopStateEvent| { let state_value: Value = event.state(); - let state: T = T::try_from(state_value).unwrap_or_default(); + let state_string: String = String::try_from(state_value).unwrap_or_default(); + let state: T = serde_json::from_str(&state_string).unwrap_or_else(|_| { + log::error!("Could not deserialize state string"); + T::default() + }); + // Can't use the existing location, because this is a callback, and can't move it in // here. @@ -99,12 +105,20 @@ where /// /// The route should be a relative path that starts with a `/`. pub fn set_route(&mut self, route: &str, state: T) { - self.history.push_state(state, "", Some(route)); + let state_string: String = serde_json::to_string(&state).unwrap_or_else(|_| { + log::error!("Could not serialize state string"); + "".to_string() + }); + self.history.push_state(state_string, "", Some(route)); } /// Replaces the route with another one removing the most recent history event and /// creating another history event in its place. pub fn replace_route(&mut self, route: &str, state: T) { - let _ = self.history.replace_state(state, "", Some(route)); + let state_string: String = serde_json::to_string(&state).unwrap_or_else(|_| { + log::error!("Could not serialize state string"); + "".to_string() + }); + let _ = self.history.replace_state(state_string, "", Some(route)); } }