From 9b3e4716bf40ee2726d04bf2a514df5f0b1befdb Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Mon, 2 Feb 2026 18:37:07 +0100 Subject: [PATCH] refactor: rework uri_encode to limit allocation add add some related tests. catched from clippy lint `format_collect` message: use of `format!` to build up a string from an iterator --> src/api/common/encoding.rs:12:17 | 12 | let value = format!("{}", c) | _____________________________^ 13 | | .bytes() 14 | | .map(|b| format!("%{:02X}", b)) 15 | | .collect::(); | |________________________________________^ | help: call `fold` instead --> src/api/common/encoding.rs:14:7 | 14 | .map(|b| format!("%{:02X}", b)) | ^^^ help: ... and use the `write!` macro here --> src/api/common/encoding.rs:14:15 | 14 | .map(|b| format!("%{:02X}", b)) | ^^^^^^^^^^^^^^^^^^^^^ = note: this can be written more efficiently by appending to a `String` directly = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#format_collect --- Cargo.toml | 1 + src/api/common/encoding.rs | 98 +++++++++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44db8c99..5aac1826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -183,6 +183,7 @@ strip = "debuginfo" [workspace.lints.clippy] # pedantic lints configuration doc_markdown = "warn" +format_collect = "warn" manual_midpoint = "warn" semicolon_if_nothing_returned = "warn" unnecessary_semicolon = "warn" diff --git a/src/api/common/encoding.rs b/src/api/common/encoding.rs index e286a784..4ff33ecc 100644 --- a/src/api/common/encoding.rs +++ b/src/api/common/encoding.rs @@ -1,5 +1,7 @@ //! Module containing various helpers for encoding +use std::fmt::Write as _; + /// Encode &str for use in a URI pub fn uri_encode(string: &str, encode_slash: bool) -> String { let mut result = String::with_capacity(string.len() * 2); @@ -9,14 +11,98 @@ pub fn uri_encode(string: &str, encode_slash: bool) -> String { '/' if encode_slash => result.push_str("%2F"), '/' if !encode_slash => result.push('/'), _ => { - result.push_str( - &format!("{}", c) - .bytes() - .map(|b| format!("%{:02X}", b)) - .collect::(), - ); + let mut buf = [0_u8; 4]; + let str = c.encode_utf8(&mut buf); + for b in str.bytes() { + write!(&mut result, "%{:02X}", b).unwrap(); + } } } } result } + +#[cfg(test)] +mod tests { + use crate::encoding::uri_encode; + + #[test] + fn test_uri_encode() { + let url1_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/documentation/reference-manual/features/", + true, + ); + assert_eq!( + &url1_encoded, + "https%3A%2F%2Fgaragehq.deuxfleurs.fr%2Fdocumentation%2Freference-manual%2Ffeatures%2F" + ); + + let url2_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/blog/2025-06-garage-v2/", + true, + ); + assert_eq!( + &url2_encoded, + "https%3A%2F%2Fgaragehq.deuxfleurs.fr%2Fblog%2F2025-06-garage-v2%2F" + ); + + let url3_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/blog/2025-06-hé_les_gens/", + true, + ); + assert_eq!( + &url3_encoded, + "https%3A%2F%2Fgaragehq.deuxfleurs.fr%2Fblog%2F2025-06-h%C3%A9_les_gens%2F" + ); + + let url4_encoded = uri_encode("/home/local user/Documents/personnel/à_blog.md", true); + assert_eq!( + &url4_encoded, + "%2Fhome%2Flocal%20user%2FDocuments%2Fpersonnel%2F%C3%A0_blog.md" + ); + } + + #[test] + fn test_uri_encode_without_slash() { + let url1_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/documentation/reference-manual/features/", + false, + ); + assert_eq!( + &url1_encoded, + "https%3A//garagehq.deuxfleurs.fr/documentation/reference-manual/features/" + ); + + let url2_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/blog/2025-06-garage-v2/", + false, + ); + assert_eq!( + &url2_encoded, + "https%3A//garagehq.deuxfleurs.fr/blog/2025-06-garage-v2/" + ); + + let url3_encoded = uri_encode( + "https://garagehq.deuxfleurs.fr/blog/2025-06-hé_les_gens/", + false, + ); + assert_eq!( + &url3_encoded, + "https%3A//garagehq.deuxfleurs.fr/blog/2025-06-h%C3%A9_les_gens/" + ); + let url4_encoded = uri_encode("/home/local user/Documents/personnel/à_blog.md", false); + assert_eq!( + &url4_encoded, + "/home/local%20user/Documents/personnel/%C3%A0_blog.md" + ); + } + + #[test] + fn test_uri_encode_most_than_double_size() { + let url_encoded = uri_encode("/home/ùàé ç/çaèù/à_êô.md", true); + assert_eq!( + &url_encoded, + "%2Fhome%2F%C3%B9%C3%A0%C3%A9%20%C3%A7%2F%C3%A7a%C3%A8%C3%B9%2F%C3%A0_%C3%AA%C3%B4.md" + ); + } +}