gloo_history/query.rs
1//! # Encoding and decoding strategies for query strings.
2//!
3//! There are various strategies to map Rust types into HTTP query strings. The [`FromQuery`] and
4//! [`ToQuery`] encode the logic for how this encoding and decoding is performed. These traits
5//! are public as a form of dependency inversion, so that you can override the decoding and
6//! encoding strategy being used.
7//!
8//! These traits are used by the [`History`](crate::History) trait, which allows for modifying the
9//! history state, and the [`Location`](crate::Location) struct, which allows for extracting the
10//! current location (and this query).
11//!
12//! ## Default Strategy
13//!
14//! By default, any Rust type that implements [`Serialize`] or [`Deserialize`](serde::Deserialize)
15//! has an implementation of [`ToQuery`] or [`FromQuery`], respectively. This implementation uses
16//! the `serde_urlencoded` crate, which implements a standards-compliant `x-www-form-urlencoded`
17//! encoder and decoder. Some patterns are not supported by this crate, for example it is not
18//! possible to serialize arrays at the moment. If this is an issue for you, consider using the
19//! `serde_qs` crate.
20//!
21//! Example:
22//!
23//! ```rust,no_run
24//! use serde::{Serialize, Deserialize};
25//! use gloo_history::{MemoryHistory, History};
26//!
27//! #[derive(Serialize)]
28//! struct Query {
29//! name: String,
30//! }
31//!
32//! let query = Query {
33//! name: "user".into(),
34//! };
35//!
36//! let history = MemoryHistory::new();
37//! history.push_with_query("index.html", &query).unwrap();
38//! ```
39//!
40//! ## Custom Strategy
41//!
42//! If desired, the [`FromQuery`] and [`ToQuery`] traits can also be manually implemented on
43//! types to customize the encoding and decoding strategies. See the documentation for these traits
44//! for more detail on how this can be done.
45use crate::error::HistoryError;
46use serde::{de::DeserializeOwned, Serialize};
47use std::borrow::Cow;
48use std::convert::{AsRef, Infallible};
49
50/// Type that can be encoded into a query string.
51pub trait ToQuery {
52 /// Error that can be returned from the conversion.
53 type Error;
54
55 /// Method to encode the query into a string.
56 fn to_query(&self) -> Result<Cow<'_, str>, Self::Error>;
57}
58
59/// Type that can be decoded from a query string.
60pub trait FromQuery {
61 /// Target type after parsing.
62 type Target;
63 /// Error that can occur while parsing.
64 type Error;
65
66 /// Decode this query string into the target type.
67 fn from_query(query: &str) -> Result<Self::Target, Self::Error>;
68}
69
70impl<T: Serialize> ToQuery for T {
71 type Error = HistoryError;
72
73 fn to_query(&self) -> Result<Cow<'_, str>, Self::Error> {
74 serde_urlencoded::to_string(self)
75 .map(Into::into)
76 .map_err(Into::into)
77 }
78}
79
80impl<T: DeserializeOwned> FromQuery for T {
81 type Target = T;
82 type Error = HistoryError;
83
84 fn from_query(query: &str) -> Result<Self::Target, Self::Error> {
85 serde_urlencoded::from_str(query).map_err(Into::into)
86 }
87}
88
89/// # Encoding for raw query strings.
90///
91/// The [`Raw`] wrapper allows for specifying a query string directly, bypassing the encoding. If
92/// you use this strategy, you need to take care to escape characters that are not allowed to
93/// appear in query strings yourself.
94#[derive(Debug, Clone)]
95pub struct Raw<T>(pub T);
96
97impl<T: AsRef<str>> ToQuery for Raw<T> {
98 type Error = Infallible;
99
100 fn to_query(&self) -> Result<Cow<'_, str>, Self::Error> {
101 Ok(self.0.as_ref().into())
102 }
103}
104
105impl<T: for<'a> From<&'a str>> FromQuery for Raw<T> {
106 type Target = T;
107 type Error = Infallible;
108
109 fn from_query(query: &str) -> Result<Self::Target, Self::Error> {
110 Ok(query.into())
111 }
112}