use alloc::string::String;
fn is_unreserved(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.' || b == b'~'
}
const HEX: &[u8; 16] = b"0123456789ABCDEF";
pub(crate) fn percent_encode(input: &str) -> String {
let mut out = String::with_capacity(input.len());
for &b in input.as_bytes() {
if is_unreserved(b) {
out.push(b as char);
} else {
out.push('%');
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0F) as usize] as char);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unreserved_passthrough() {
assert_eq!(percent_encode("abc123"), "abc123");
assert_eq!(percent_encode("a-b_c.d~e"), "a-b_c.d~e");
}
#[test]
fn test_space() {
assert_eq!(percent_encode("hello world"), "hello%20world");
}
#[test]
fn test_special_chars() {
assert_eq!(percent_encode("a@b"), "a%40b");
assert_eq!(percent_encode("key=val"), "key%3Dval");
assert_eq!(percent_encode("a&b"), "a%26b");
}
#[test]
fn test_empty() {
assert_eq!(percent_encode(""), "");
}
#[test]
fn test_unicode() {
assert_eq!(percent_encode("café"), "caf%C3%A9");
}
}