pub fn urlencode(input: &str) -> String {
input
.bytes()
.map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
char::from(b).to_string()
}
_ => format!("%{:02X}", b),
})
.collect()
}
pub fn truncate_chars(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_string()
} else {
s.chars().take(max).collect()
}
}
pub fn percent_decode(input: &str) -> String {
let mut result = Vec::with_capacity(input.len());
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
let hex = &input[i + 1..i + 3];
if let Ok(byte) = u8::from_str_radix(hex, 16) {
result.push(byte);
i += 3;
continue;
}
} else if bytes[i] == b'+' {
result.push(b' ');
i += 1;
continue;
}
result.push(bytes[i]);
i += 1;
}
String::from_utf8_lossy(&result).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_urlencode() {
assert_eq!(urlencode("hello world"), "hello%20world");
assert_eq!(urlencode("rust-lang"), "rust-lang");
assert_eq!(urlencode("café"), "caf%C3%A9");
}
#[test]
fn test_truncate_chars() {
assert_eq!(truncate_chars("hello", 10), "hello");
assert_eq!(truncate_chars("Hello World", 2), "He");
let s = "a🌍b".repeat(50);
let truncated = truncate_chars(&s, 10);
assert!(std::str::from_utf8(truncated.as_bytes()).is_ok());
}
#[test]
fn test_percent_decode() {
assert_eq!(
percent_decode("https%3A%2F%2Fexample.com"),
"https://example.com"
);
assert_eq!(percent_decode("hello+world"), "hello world");
}
}