Skip to content

Commit 1be0ee7

Browse files
committed
Merge branch 'main' into feature/tostring
2 parents 7f75d1c + a2c0a13 commit 1be0ee7

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

.github/workflows/rust.yml

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,69 @@
1+
---
12
name: Rust
23

34
on:
45
push:
56
branches: [ "main" ]
7+
paths:
8+
- 'Cargo.toml'
9+
- 'Cargo.lock'
10+
- 'src/**'
11+
- 'crates/**'
12+
- 'bins/**'
13+
- '.github/workflows/rust.yml'
614
pull_request:
715
branches: [ "main" ]
16+
paths:
17+
- 'Cargo.toml'
18+
- 'Cargo.lock'
19+
- 'src/**'
20+
- 'crates/**'
21+
- 'bins/**'
22+
- '.github/workflows/rust.yml'
823

924
env:
1025
CARGO_TERM_COLOR: always
1126

1227
jobs:
1328
build:
29+
name: Build and Test
30+
runs-on: ${{ matrix.os }}
31+
strategy:
32+
matrix:
33+
os: [ ubuntu-latest, macos-latest, windows-latest ]
34+
steps:
35+
- uses: actions/checkout@v4
36+
- name: Check format
37+
run: cargo fmt --check
38+
- name: Build
39+
run: cargo build --verbose
40+
- uses: taiki-e/install-action@nextest
41+
- name: Run tests
42+
run: cargo nextest run --verbose --all-features
43+
- name: Run doctests
44+
run: cargo test --doc --verbose --all-features
1445

46+
codecov:
47+
name: Code Coverage
1548
runs-on: ubuntu-latest
16-
49+
env:
50+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1751
steps:
18-
- uses: actions/checkout@v3
19-
- name: Build
20-
run: cargo build --verbose
21-
- name: Run tests
22-
run: cargo test --tests --verbose
23-
- name: Run doctests
24-
run: cargo test --doc --verbose
52+
- uses: actions/checkout@v4
53+
- name: Build
54+
run: cargo build --verbose
55+
- uses: dtolnay/rust-toolchain@stable
56+
with:
57+
components: llvm-tools-preview
58+
- name: Install cargo-llvm-cov
59+
uses: taiki-e/install-action@cargo-llvm-cov
60+
- name: Install nextest
61+
uses: taiki-e/install-action@nextest
62+
- name: Generate code coverage
63+
run: cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info
64+
- name: Upload coverage to Codecov
65+
uses: codecov/codecov-action@v4.0.1
66+
with:
67+
token: ${{ secrets.CODECOV_TOKEN }}
68+
files: lcov.info
69+
fail_ci_if_error: true

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# A query string builder for percent encoding key-value pairs
22

3+
[![Crates.io](https://img.shields.io/crates/v/query-string-builder)](https://crates.io/crates/query-string-builder)
4+
[![Crates.io](https://img.shields.io/crates/l/query-string-builder)](https://crates.io/crates/query-string-builder)
5+
[![codecov](https://codecov.io/gh/sunsided/query-string-builder/graph/badge.svg?token=HUCXM04DOG)](https://codecov.io/gh/sunsided/query-string-builder)
6+
37
This is a tiny helper crate for simplifying the construction of URL query strings.
48
The initial `?` question mark is automatically prepended.
59

src/lib.rs

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

25-
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
2625
use std::fmt::{Debug, Display, Formatter};
2726

27+
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
28+
2829
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
2930
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
3031

@@ -100,11 +101,7 @@ impl QueryString {
100101
/// "https://example.com/?q=%F0%9F%8D%8E%20apple&category=fruits%20and%20vegetables&works=true"
101102
/// );
102103
/// ```
103-
pub fn with_opt_value<K: ToString, V: ToString>(
104-
self,
105-
key: K,
106-
value: Option<V>,
107-
) -> Self {
104+
pub fn with_opt_value<K: ToString, V: ToString>(self, key: K, value: Option<V>) -> Self {
108105
if let Some(value) = value {
109106
self.with_value(key, value)
110107
} else {
@@ -152,11 +149,7 @@ impl QueryString {
152149
/// "https://example.com/?q=%F0%9F%8D%8E%20apple"
153150
/// );
154151
/// ```
155-
pub fn push_opt<K: ToString, V: ToString>(
156-
&mut self,
157-
key: K,
158-
value: Option<V>,
159-
) -> &Self {
152+
pub fn push_opt<K: ToString, V: ToString>(&mut self, key: K, value: Option<V>) -> &Self {
160153
if let Some(value) = value {
161154
self.push(key, value)
162155
} else {
@@ -221,7 +214,7 @@ impl QueryString {
221214
impl Display for QueryString {
222215
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
223216
if self.pairs.is_empty() {
224-
return Ok(());
217+
Ok(())
225218
} else {
226219
write!(f, "?")?;
227220
for (i, pair) in self.pairs.iter().enumerate() {
@@ -250,6 +243,14 @@ struct Kvp {
250243
mod tests {
251244
use super::*;
252245

246+
#[test]
247+
fn test_empty() {
248+
let qs = QueryString::new();
249+
assert_eq!(qs.to_string(), "");
250+
assert_eq!(qs.len(), 0);
251+
assert!(qs.is_empty());
252+
}
253+
253254
#[test]
254255
fn test_simple() {
255256
let qs = QueryString::new()
@@ -259,6 +260,8 @@ mod tests {
259260
qs.to_string(),
260261
"?q=apple???&category=fruits%20and%20vegetables"
261262
);
263+
assert_eq!(qs.len(), 2);
264+
assert!(!qs.is_empty());
262265
}
263266

264267
#[test]
@@ -267,7 +270,10 @@ mod tests {
267270
.with_value("q", "Grünkohl")
268271
.with_value("category", "Gemüse")
269272
.with_value("answer", 42);
270-
assert_eq!(qs.to_string(), "?q=Gr%C3%BCnkohl&category=Gem%C3%BCse&answer=42");
273+
assert_eq!(
274+
qs.to_string(),
275+
"?q=Gr%C3%BCnkohl&category=Gem%C3%BCse&answer=42"
276+
);
271277
}
272278

273279
#[test]
@@ -291,5 +297,33 @@ mod tests {
291297
qs.to_string(),
292298
"?q=celery&category=fruits%20and%20vegetables"
293299
);
300+
assert_eq!(qs.len(), 2); // not three!
301+
}
302+
303+
#[test]
304+
fn test_push_optional() {
305+
let mut qs = QueryString::new();
306+
qs.push("a", "apple");
307+
qs.push_opt("b", None::<String>);
308+
qs.push_opt("c", Some("🍎 apple"));
309+
310+
assert_eq!(
311+
format!("https://example.com/{qs}"),
312+
"https://example.com/?a=apple&c=%F0%9F%8D%8E%20apple"
313+
);
314+
}
315+
316+
#[test]
317+
fn test_append() {
318+
let qs = QueryString::new().with_value("q", "apple");
319+
let more = QueryString::new().with_value("q", "pear");
320+
321+
let mut qs = qs.append_into(more);
322+
qs.append(QueryString::new().with_value("answer", "42"));
323+
324+
assert_eq!(
325+
format!("https://example.com/{qs}"),
326+
"https://example.com/?q=apple&q=pear&answer=42"
327+
);
294328
}
295329
}

0 commit comments

Comments
 (0)