Skip to content

Commit 8cf1106

Browse files
committed
Add draft for lazily evaluated values
1 parent 684da0e commit 8cf1106

File tree

1 file changed

+142
-7
lines changed

1 file changed

+142
-7
lines changed

src/lib.rs

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
2323
#![deny(unsafe_code)]
2424

25-
use std::fmt::{Debug, Display, Formatter};
25+
use std::fmt::{Display, Formatter};
26+
use std::rc::Rc;
27+
use std::sync::Arc;
2628

2729
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
2830

@@ -45,7 +47,7 @@ const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').ad
4547
/// "https://example.com/?q=apple&category=fruits%20and%20vegetables"
4648
/// );
4749
/// ```
48-
#[derive(Debug, Default, Clone)]
50+
#[derive(Default)]
4951
pub struct QueryString {
5052
pairs: Vec<Kvp>,
5153
}
@@ -78,7 +80,33 @@ impl QueryString {
7880
pub fn with_value<K: ToString, V: ToString>(mut self, key: K, value: V) -> Self {
7981
self.pairs.push(Kvp {
8082
key: key.to_string(),
81-
value: value.to_string(),
83+
value: Value::eager(value),
84+
});
85+
self
86+
}
87+
88+
/// Appends a key-value pair to the query string. Supports lazy evaluation.
89+
///
90+
/// ## Example
91+
///
92+
/// ```
93+
/// use std::sync::Arc;
94+
/// use query_string_builder::{QueryString, Value};
95+
///
96+
/// let qs = QueryString::new()
97+
/// .with("q", Value::eager("🍎 apple"))
98+
/// .with("category", || "fruits and vegetables")
99+
/// .with("answer", Arc::new(42));
100+
///
101+
/// assert_eq!(
102+
/// format!("https://example.com/{qs}"),
103+
/// "https://example.com/?q=%F0%9F%8D%8E%20apple&category=fruits%20and%20vegetables&answer=42"
104+
/// );
105+
/// ```
106+
pub fn with<K: ToString, V: Into<Value>>(mut self, key: K, value: V) -> Self {
107+
self.pairs.push(Kvp {
108+
key: key.to_string(),
109+
value: value.into(),
82110
});
83111
self
84112
}
@@ -128,7 +156,7 @@ impl QueryString {
128156
pub fn push<K: ToString, V: ToString>(&mut self, key: K, value: V) -> &Self {
129157
self.pairs.push(Kvp {
130158
key: key.to_string(),
131-
value: value.to_string(),
159+
value: Value::eager(value),
132160
});
133161
self
134162
}
@@ -221,22 +249,102 @@ impl Display for QueryString {
221249
if i > 0 {
222250
write!(f, "&")?;
223251
}
252+
253+
let value = pair.value.render();
224254
write!(
225255
f,
226256
"{key}={value}",
227257
key = utf8_percent_encode(&pair.key, FRAGMENT),
228-
value = utf8_percent_encode(&pair.value, FRAGMENT)
258+
value = utf8_percent_encode(&value, FRAGMENT)
229259
)?;
230260
}
231261
Ok(())
232262
}
233263
}
234264
}
235265

236-
#[derive(Debug, Clone)]
237266
struct Kvp {
238267
key: String,
239-
value: String,
268+
value: Value,
269+
}
270+
271+
pub struct Value {
272+
value: Box<dyn Fn() -> String>,
273+
}
274+
275+
impl Value {
276+
pub fn eager<T: ToString>(value: T) -> Self {
277+
let value = value.to_string();
278+
Value {
279+
value: Box::new(move || value.to_string()),
280+
}
281+
}
282+
283+
pub fn lazy<T: ToString + 'static>(value: T) -> Self {
284+
Value {
285+
value: Box::new(move || value.to_string()),
286+
}
287+
}
288+
289+
pub fn lazy_box<T: ToString + 'static>(value: Box<T>) -> Self {
290+
Value {
291+
value: Box::new(move || value.to_string()),
292+
}
293+
}
294+
295+
pub fn lazy_rc<T: ToString + 'static>(value: Rc<T>) -> Self {
296+
Value {
297+
value: Box::new(move || value.to_string()),
298+
}
299+
}
300+
301+
pub fn lazy_arc<T: ToString + 'static>(value: Arc<T>) -> Self {
302+
Value {
303+
value: Box::new(move || value.to_string()),
304+
}
305+
}
306+
307+
pub fn lazy_fn<F, T>(func: F) -> Self
308+
where
309+
F: Fn() -> T + 'static,
310+
T: ToString + 'static,
311+
{
312+
Value {
313+
value: Box::new(move || func().to_string()),
314+
}
315+
}
316+
317+
fn render(&self) -> String {
318+
(self.value)()
319+
}
320+
}
321+
322+
impl<T> From<Rc<T>> for Value
323+
where
324+
T: ToString + 'static,
325+
{
326+
fn from(value: Rc<T>) -> Self {
327+
Value::lazy_rc(value)
328+
}
329+
}
330+
331+
impl<T> From<Arc<T>> for Value
332+
where
333+
T: ToString + 'static,
334+
{
335+
fn from(value: Arc<T>) -> Self {
336+
Value::lazy_arc(value)
337+
}
338+
}
339+
340+
impl<F, T> From<F> for Value
341+
where
342+
F: Fn() -> T + 'static,
343+
T: ToString + 'static,
344+
{
345+
fn from(value: F) -> Self {
346+
Value::lazy_fn(value)
347+
}
240348
}
241349

242350
#[cfg(test)]
@@ -266,6 +374,33 @@ mod tests {
266374
assert!(!qs.is_empty());
267375
}
268376

377+
#[test]
378+
fn test_lazy() {
379+
let qs = QueryString::new()
380+
.with("x", Value::eager("y"))
381+
.with("q", Value::lazy("apple???"))
382+
.with("category", Value::lazy_fn(|| "fruits and vegetables"))
383+
.with("tasty", Value::lazy_box(Box::new(true)))
384+
.with("weight", Value::lazy_arc(Arc::new(99.9)));
385+
assert_eq!(
386+
qs.to_string(),
387+
"?x=y&q=apple???&category=fruits%20and%20vegetables&tasty=true&weight=99.9"
388+
);
389+
}
390+
391+
#[test]
392+
fn test_lazy_implicit() {
393+
let qs = QueryString::new()
394+
.with("q", Value::lazy("apple???"))
395+
.with("category", || "fruits and vegetables")
396+
.with("tasty", Rc::new(true))
397+
.with("weight", Arc::new(99.9));
398+
assert_eq!(
399+
qs.to_string(),
400+
"?q=apple???&category=fruits%20and%20vegetables&tasty=true&weight=99.9"
401+
);
402+
}
403+
269404
#[test]
270405
fn test_encoding() {
271406
let qs = QueryString::new()

0 commit comments

Comments
 (0)