Skip to content

Commit

Permalink
Introduces the FromQuery and IntoQuery traits.
Browse files Browse the repository at this point in the history
These two traits are introduced to allow for customizing the query
encoding and decoding behaviour by implementing these traits for custom
types. The previous behaviour of using serde_urlencoded for anything
that implements Serialize and Deserialize is preserved. Customt wrapper
types are added which modify the encoding and decoding behaviour. An
optional feature named `query-qs` is introduced which allows for
optionally encoding and decoding query strings using serde_qs.
  • Loading branch information
xfbs committed Aug 10, 2023
1 parent e4f3a2e commit 4436c69
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 91 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
### Next Version

- Migrate to Edition 2021 and Apply MSRV in Cargo.toml (#360)
- Introduces the `FromQuery` and `ToQuery` traits to allow for customizing
how query strings are encoded and decoded in `gloo_history`. (#364)

### Version "0.2.3"

Expand Down
27 changes: 14 additions & 13 deletions crates/history/src/any.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
use std::borrow::Cow;

#[cfg(feature = "query")]
use serde::Serialize;

use crate::browser::BrowserHistory;
#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::hash::HashHistory;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::memory::MemoryHistory;
#[cfg(feature = "query")]
use crate::{error::HistoryResult, query::ToQuery};

/// A [`History`] that provides a universal API to the underlying history type.
#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -79,9 +76,13 @@ impl History for AnyHistory {
}

#[cfg(feature = "query")]
fn push_with_query<'a, Q>(&self, route: impl Into<Cow<'a, str>>, query: Q) -> HistoryResult<()>
fn push_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
match self {
Self::Browser(m) => m.push_with_query(route, query),
Expand All @@ -94,9 +95,9 @@ impl History for AnyHistory {
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
match self {
Self::Browser(m) => m.replace_with_query(route, query),
Expand All @@ -111,9 +112,9 @@ impl History for AnyHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
match self {
Expand All @@ -129,9 +130,9 @@ impl History for AnyHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
match self {
Expand Down
40 changes: 19 additions & 21 deletions crates/history/src/browser.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
use std::any::Any;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use std::{any::Any, borrow::Cow, cell::RefCell, fmt, rc::Rc};

use gloo_events::EventListener;
use gloo_utils::window;
#[cfg(feature = "query")]
use serde::Serialize;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use web_sys::Url;

#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::state::{HistoryState, StateMap};
use crate::utils::WeakCallback;
#[cfg(feature = "query")]
use crate::{error::HistoryResult, query::ToQuery};

/// A [`History`] that is implemented with [`web_sys::History`] that provides native browser
/// history and state access.
Expand Down Expand Up @@ -109,12 +103,16 @@ impl History for BrowserHistory {
}

#[cfg(feature = "query")]
fn push_with_query<'a, Q>(&self, route: impl Into<Cow<'a, str>>, query: Q) -> HistoryResult<()>
fn push_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
let route = route.into();
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;

let url = Self::combine_url(&route, &query);

Expand All @@ -131,12 +129,12 @@ impl History for BrowserHistory {
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
let route = route.into();
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;

let url = Self::combine_url(&route, &query);

Expand All @@ -154,9 +152,9 @@ impl History for BrowserHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
let (id, history_state) = Self::create_history_state();
Expand All @@ -165,7 +163,7 @@ impl History for BrowserHistory {
states.insert(id, Rc::new(state) as Rc<dyn Any>);

let route = route.into();
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;

let url = Self::combine_url(&route, &query);

Expand All @@ -184,9 +182,9 @@ impl History for BrowserHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
let (id, history_state) = Self::create_history_state();
Expand All @@ -195,7 +193,7 @@ impl History for BrowserHistory {
states.insert(id, Rc::new(state) as Rc<dyn Any>);

let route = route.into();
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;

let url = Self::combine_url(&route, &query);

Expand Down
2 changes: 1 addition & 1 deletion crates/history/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ pub enum HistoryError {
}

/// The Result type for History.
pub type HistoryResult<T> = std::result::Result<T, HistoryError>;
pub type HistoryResult<T, E = HistoryError> = std::result::Result<T, E>;
37 changes: 19 additions & 18 deletions crates/history/src/hash.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
use std::borrow::Cow;
use std::fmt;
use std::{borrow::Cow, fmt};

use gloo_utils::window;
#[cfg(feature = "query")]
use serde::Serialize;
use wasm_bindgen::UnwrapThrowExt;
use web_sys::Url;

use crate::browser::BrowserHistory;
#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::utils::{assert_absolute_path, assert_no_query};
#[cfg(feature = "query")]
use crate::{error::HistoryResult, query::ToQuery};

/// A [`History`] that is implemented with [`web_sys::History`] and stores path in `#`(fragment).
///
Expand Down Expand Up @@ -95,11 +92,15 @@ impl History for HashHistory {
}

#[cfg(feature = "query")]
fn push_with_query<'a, Q>(&self, route: impl Into<Cow<'a, str>>, query: Q) -> HistoryResult<()>
fn push_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;
let route = route.into();

assert_absolute_path(&route);
Expand All @@ -116,11 +117,11 @@ impl History for HashHistory {
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
{
let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;
let route = route.into();

assert_absolute_path(&route);
Expand All @@ -139,9 +140,9 @@ impl History for HashHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
let route = route.into();
Expand All @@ -151,7 +152,7 @@ impl History for HashHistory {

let url = Self::get_url();

let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;
url.set_hash(&format!("{route}?{query}"));

self.inner.push_with_state(url.href(), state);
Expand All @@ -165,9 +166,9 @@ impl History for HashHistory {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static,
{
let route = route.into();
Expand All @@ -177,7 +178,7 @@ impl History for HashHistory {

let url = Self::get_url();

let query = serde_urlencoded::to_string(query)?;
let query = query.to_query()?;
url.set_hash(&format!("{route}?{query}"));

self.inner.replace_with_state(url.href(), state);
Expand Down
27 changes: 14 additions & 13 deletions crates/history/src/history.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use std::borrow::Cow;

#[cfg(feature = "query")]
use serde::Serialize;

#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::listener::HistoryListener;
use crate::location::Location;
#[cfg(feature = "query")]
use crate::{error::HistoryResult, query::ToQuery};

/// A trait to provide [`History`] access.
///
Expand Down Expand Up @@ -56,19 +53,23 @@ pub trait History: Clone + PartialEq {

/// Same as `.push()` but affix the queries to the end of the route.
#[cfg(feature = "query")]
fn push_with_query<'a, Q>(&self, route: impl Into<Cow<'a, str>>, query: Q) -> HistoryResult<()>
fn push_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<(), Q::Error>
where
Q: Serialize;
Q: ToQuery;

/// Same as `.replace()` but affix the queries to the end of the route.
#[cfg(feature = "query")]
fn replace_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize;
Q: ToQuery;

/// Same as `.push_with_state()` but affix the queries to the end of the route.
#[cfg(feature = "query")]
Expand All @@ -77,9 +78,9 @@ pub trait History: Clone + PartialEq {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static;

/// Same as `.replace_with_state()` but affix the queries to the end of the route.
Expand All @@ -89,9 +90,9 @@ pub trait History: Clone + PartialEq {
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
) -> HistoryResult<(), Q::Error>
where
Q: Serialize,
Q: ToQuery,
T: 'static;

/// Creates a Listener that will be notified when current state changes.
Expand Down
2 changes: 2 additions & 0 deletions crates/history/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ mod history;
mod listener;
mod location;
mod memory;
#[cfg(feature = "query")]
pub mod query;
mod state;
mod utils;

Expand Down
13 changes: 5 additions & 8 deletions crates/history/src/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use std::any::Any;
use std::rc::Rc;

#[cfg(feature = "query")]
use serde::de::DeserializeOwned;

#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::{error::HistoryResult, query::FromQuery};

/// A history location.
///
Expand Down Expand Up @@ -44,12 +41,12 @@ impl Location {

/// Returns the queries of current URL parsed as `T`.
#[cfg(feature = "query")]
pub fn query<T>(&self) -> HistoryResult<T>
pub fn query<T>(&self) -> HistoryResult<T::Target, T::Error>
where
T: DeserializeOwned,
T: FromQuery,
{
let query = self.query_str();
serde_urlencoded::from_str(query.strip_prefix('?').unwrap_or("")).map_err(|e| e.into())
let query = self.query_str().strip_prefix('?').unwrap_or("");
T::from_query(query)
}

/// Returns the hash fragment of current URL.
Expand Down
Loading

0 comments on commit 4436c69

Please sign in to comment.