Skip to content

Commit adc0a3c

Browse files
authored
Merge pull request #12 from sunsided/feature/slim-builder
Add a slim builder that doesn't call `to_string` right away
2 parents 873c41d + 8722770 commit adc0a3c

File tree

8 files changed

+611
-28
lines changed

8 files changed

+611
-28
lines changed

.codespellrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[codespell]
2+
ignore-words-list = crate
3+
skip = .git,*.lock

.github/workflows/codespell.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
name: Codespell
3+
4+
on:
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
branches: [ main ]
9+
10+
jobs:
11+
codespell:
12+
name: Check for spelling errors
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
- name: Codespell
19+
uses: codespell-project/actions-codespell@v2

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6+
## Unreleased
7+
8+
### Added
9+
10+
- The `QueryString::simple` function was added to construct the new `QueryStringSimple` type.
11+
This type reduces string allocations, defers rendering and can keep references
12+
but at the cost of a complex type signature slightly more rigid handling.
13+
14+
### Changed
15+
16+
- The `QueryString::new` function was renamed to `QueryString::dynamic`.
17+
618
## [0.5.1] - 2024-05-24
719

820
[0.5.1]: https://github.com/sunsided/query-string-builder/releases/tag/v0.5.1

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ criterion = "0.5.1"
1919
[[bench]]
2020
name = "bench"
2121
harness = false
22+
23+
[[bench]]
24+
name = "bench_slim"
25+
harness = false

benches/bench.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
66
// `with_value` method benchmark
77
c.bench_function("with_value", |b| {
88
b.iter(|| {
9-
let qs = QueryString::new()
9+
let qs = QueryString::dynamic()
1010
.with_value("q", "apple???")
1111
.with_value("category", "fruits and vegetables");
1212
format!("{qs}")
@@ -16,7 +16,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
1616
// `with_opt_value` method benchmark
1717
c.bench_function("with_opt_value", |b| {
1818
b.iter(|| {
19-
let qs = QueryString::new()
19+
let qs = QueryString::dynamic()
2020
.with_value("q", "celery")
2121
.with_opt_value("taste", None::<String>)
2222
.with_opt_value("category", Some("fruits and vegetables"))
@@ -29,12 +29,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
2929
// Full test including creating, pushing and appending
3030
c.bench_function("push_opt_and_append", |b| {
3131
b.iter(|| {
32-
let mut qs = QueryString::new();
32+
let mut qs = QueryString::dynamic();
3333
qs.push("a", "apple");
3434
qs.push_opt("b", None::<String>);
3535
qs.push_opt("c", Some("🍎 apple"));
3636

37-
let more = QueryString::new().with_value("q", "pear");
37+
let more = QueryString::dynamic().with_value("q", "pear");
3838
let qs = qs.append_into(more);
3939

4040
format!("{qs}")

benches/bench_slim.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
3+
use query_string_builder::QueryString;
4+
5+
pub fn criterion_benchmark(c: &mut Criterion) {
6+
// `with_value` method benchmark
7+
c.bench_function("with_value (slim)", |b| {
8+
b.iter(|| {
9+
let qs = QueryString::simple()
10+
.with_value("q", "apple???")
11+
.with_value("category", "fruits and vegetables");
12+
format!("{qs}")
13+
})
14+
});
15+
16+
// `with_opt_value` method benchmark
17+
c.bench_function("with_opt_value (slim)", |b| {
18+
b.iter(|| {
19+
let qs = QueryString::simple()
20+
.with_value("q", "celery")
21+
.with_opt_value("taste", None::<String>)
22+
.with_opt_value("category", Some("fruits and vegetables"))
23+
.with_opt_value("tasty", Some(true))
24+
.with_opt_value("weight", Some(99.9));
25+
format!("{qs}")
26+
})
27+
});
28+
}
29+
30+
criterion_group!(benches, criterion_benchmark);
31+
criterion_main!(benches);

src/lib.rs

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! ```
99
//! use query_string_builder::QueryString;
1010
//!
11-
//! let qs = QueryString::new()
11+
//! let qs = QueryString::dynamic()
1212
//! .with_value("q", "🍎 apple")
1313
//! .with_value("tasty", true)
1414
//! .with_opt_value("color", None::<String>)
@@ -22,12 +22,15 @@
2222
2323
#![deny(unsafe_code)]
2424

25-
use std::fmt::{Debug, Display, Formatter, Write};
25+
mod slim;
2626

2727
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
28+
use std::fmt::{Debug, Display, Formatter, Write};
29+
30+
pub use slim::{QueryStringSimple, WrappedQueryString};
2831

2932
/// https://url.spec.whatwg.org/#query-percent-encode-set
30-
const QUERY: &AsciiSet = &CONTROLS
33+
pub(crate) const QUERY: &AsciiSet = &CONTROLS
3134
.add(b' ')
3235
.add(b'"')
3336
.add(b'#')
@@ -50,7 +53,7 @@ const QUERY: &AsciiSet = &CONTROLS
5053
/// ```
5154
/// use query_string_builder::QueryString;
5255
///
53-
/// let qs = QueryString::new()
56+
/// let qs = QueryString::dynamic()
5457
/// .with_value("q", "apple")
5558
/// .with_value("category", "fruits and vegetables");
5659
///
@@ -59,14 +62,38 @@ const QUERY: &AsciiSet = &CONTROLS
5962
/// "https://example.com/?q=apple&category=fruits%20and%20vegetables"
6063
/// );
6164
/// ```
62-
#[derive(Debug, Default, Clone)]
65+
#[derive(Debug, Clone)]
6366
pub struct QueryString {
6467
pairs: Vec<Kvp>,
6568
}
6669

6770
impl QueryString {
6871
/// Creates a new, empty query string builder.
69-
pub fn new() -> Self {
72+
///
73+
/// ## Example
74+
///
75+
/// ```
76+
/// use query_string_builder::QueryString;
77+
///
78+
/// let weight: &f32 = &99.9;
79+
///
80+
/// let qs = QueryString::simple()
81+
/// .with_value("q", "apple")
82+
/// .with_value("category", "fruits and vegetables")
83+
/// .with_opt_value("weight", Some(weight));
84+
///
85+
/// assert_eq!(
86+
/// format!("https://example.com/{qs}"),
87+
/// "https://example.com/?q=apple&category=fruits%20and%20vegetables&weight=99.9"
88+
/// );
89+
/// ```
90+
#[allow(clippy::new_ret_no_self)]
91+
pub fn simple() -> QueryStringSimple {
92+
QueryStringSimple::default()
93+
}
94+
95+
/// Creates a new, empty query string builder.
96+
pub fn dynamic() -> Self {
7097
Self {
7198
pairs: Vec::default(),
7299
}
@@ -79,7 +106,7 @@ impl QueryString {
79106
/// ```
80107
/// use query_string_builder::QueryString;
81108
///
82-
/// let qs = QueryString::new()
109+
/// let qs = QueryString::dynamic()
83110
/// .with_value("q", "🍎 apple")
84111
/// .with_value("category", "fruits and vegetables")
85112
/// .with_value("answer", 42);
@@ -104,7 +131,7 @@ impl QueryString {
104131
/// ```
105132
/// use query_string_builder::QueryString;
106133
///
107-
/// let qs = QueryString::new()
134+
/// let qs = QueryString::dynamic()
108135
/// .with_opt_value("q", Some("🍎 apple"))
109136
/// .with_opt_value("f", None::<String>)
110137
/// .with_opt_value("category", Some("fruits and vegetables"))
@@ -130,7 +157,7 @@ impl QueryString {
130157
/// ```
131158
/// use query_string_builder::QueryString;
132159
///
133-
/// let mut qs = QueryString::new();
160+
/// let mut qs = QueryString::dynamic();
134161
/// qs.push("q", "apple");
135162
/// qs.push("category", "fruits and vegetables");
136163
///
@@ -154,7 +181,7 @@ impl QueryString {
154181
/// ```
155182
/// use query_string_builder::QueryString;
156183
///
157-
/// let mut qs = QueryString::new();
184+
/// let mut qs = QueryString::dynamic();
158185
/// qs.push_opt("q", None::<String>);
159186
/// qs.push_opt("q", Some("🍎 apple"));
160187
///
@@ -188,8 +215,8 @@ impl QueryString {
188215
/// ```
189216
/// use query_string_builder::QueryString;
190217
///
191-
/// let mut qs = QueryString::new().with_value("q", "apple");
192-
/// let more = QueryString::new().with_value("q", "pear");
218+
/// let mut qs = QueryString::dynamic().with_value("q", "apple");
219+
/// let more = QueryString::dynamic().with_value("q", "pear");
193220
///
194221
/// qs.append(more);
195222
///
@@ -209,8 +236,8 @@ impl QueryString {
209236
/// ```
210237
/// use query_string_builder::QueryString;
211238
///
212-
/// let qs = QueryString::new().with_value("q", "apple");
213-
/// let more = QueryString::new().with_value("q", "pear");
239+
/// let qs = QueryString::dynamic().with_value("q", "apple");
240+
/// let more = QueryString::dynamic().with_value("q", "pear");
214241
///
215242
/// let qs = qs.append_into(more);
216243
///
@@ -257,15 +284,15 @@ mod tests {
257284

258285
#[test]
259286
fn test_empty() {
260-
let qs = QueryString::new();
287+
let qs = QueryStringSimple::default();
261288
assert_eq!(qs.to_string(), "");
262289
assert_eq!(qs.len(), 0);
263290
assert!(qs.is_empty());
264291
}
265292

266293
#[test]
267294
fn test_simple() {
268-
let qs = QueryString::new()
295+
let qs = QueryString::dynamic()
269296
.with_value("q", "apple???")
270297
.with_value("category", "fruits and vegetables")
271298
.with_value("tasty", true)
@@ -280,15 +307,15 @@ mod tests {
280307

281308
#[test]
282309
fn test_encoding() {
283-
let qs = QueryString::new()
310+
let qs = QueryString::dynamic()
284311
.with_value("q", "Grünkohl")
285312
.with_value("category", "Gemüse");
286313
assert_eq!(qs.to_string(), "?q=Gr%C3%BCnkohl&category=Gem%C3%BCse");
287314
}
288315

289316
#[test]
290317
fn test_emoji() {
291-
let qs = QueryString::new()
318+
let qs = QueryString::dynamic()
292319
.with_value("q", "🥦")
293320
.with_value("🍽️", "🍔🍕");
294321
assert_eq!(
@@ -299,7 +326,7 @@ mod tests {
299326

300327
#[test]
301328
fn test_optional() {
302-
let qs = QueryString::new()
329+
let qs = QueryString::dynamic()
303330
.with_value("q", "celery")
304331
.with_opt_value("taste", None::<String>)
305332
.with_opt_value("category", Some("fruits and vegetables"))
@@ -314,7 +341,7 @@ mod tests {
314341

315342
#[test]
316343
fn test_push_optional() {
317-
let mut qs = QueryString::new();
344+
let mut qs = QueryString::dynamic();
318345
qs.push("a", "apple");
319346
qs.push_opt("b", None::<String>);
320347
qs.push_opt("c", Some("🍎 apple"));
@@ -327,11 +354,11 @@ mod tests {
327354

328355
#[test]
329356
fn test_append() {
330-
let qs = QueryString::new().with_value("q", "apple");
331-
let more = QueryString::new().with_value("q", "pear");
357+
let qs = QueryString::dynamic().with_value("q", "apple");
358+
let more = QueryString::dynamic().with_value("q", "pear");
332359

333360
let mut qs = qs.append_into(more);
334-
qs.append(QueryString::new().with_value("answer", "42"));
361+
qs.append(QueryString::dynamic().with_value("answer", "42"));
335362

336363
assert_eq!(
337364
format!("https://example.com/{qs}"),
@@ -371,7 +398,7 @@ mod tests {
371398
("right_curly", "}", "}"),
372399
];
373400

374-
let mut qs = QueryString::new();
401+
let mut qs = QueryString::dynamic();
375402
for (key, value, _) in &tests {
376403
qs.push(key.to_string(), value.to_string());
377404
}

0 commit comments

Comments
 (0)