use std::fmt;
pub fn for_uri_component(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_uri_component(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_uri_component<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
for byte in input.as_bytes() {
if is_unreserved(*byte) {
out.write_char(*byte as char)?;
} else {
write!(out, "%{:02X}", byte)?;
}
}
Ok(())
}
fn is_unreserved(b: u8) -> bool {
matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uri_component_no_encoding_needed() {
assert_eq!(for_uri_component("hello"), "hello");
assert_eq!(for_uri_component(""), "");
assert_eq!(for_uri_component("ABCxyz019"), "ABCxyz019");
assert_eq!(for_uri_component("-._~"), "-._~");
}
#[test]
fn uri_component_encodes_space() {
assert_eq!(for_uri_component("a b"), "a%20b");
}
#[test]
fn uri_component_encodes_reserved_chars() {
assert_eq!(for_uri_component("a=b"), "a%3Db");
assert_eq!(for_uri_component("a&b"), "a%26b");
assert_eq!(for_uri_component("a+b"), "a%2Bb");
assert_eq!(for_uri_component("a?b"), "a%3Fb");
assert_eq!(for_uri_component("a#b"), "a%23b");
assert_eq!(for_uri_component("a/b"), "a%2Fb");
}
#[test]
fn uri_component_encodes_html_significant() {
assert_eq!(for_uri_component("<script>"), "%3Cscript%3E");
assert_eq!(for_uri_component(r#""quoted""#), "%22quoted%22");
}
#[test]
fn uri_component_encodes_two_byte_utf8() {
assert_eq!(for_uri_component("\u{00A0}"), "%C2%A0");
assert_eq!(for_uri_component("é"), "%C3%A9");
}
#[test]
fn uri_component_encodes_three_byte_utf8() {
assert_eq!(for_uri_component("\u{0800}"), "%E0%A0%80");
assert_eq!(for_uri_component("世"), "%E4%B8%96");
}
#[test]
fn uri_component_encodes_four_byte_utf8() {
assert_eq!(for_uri_component("\u{10000}"), "%F0%90%80%80");
assert_eq!(for_uri_component("😀"), "%F0%9F%98%80");
}
#[test]
fn uri_component_encodes_control_chars() {
assert_eq!(for_uri_component("\x00"), "%00");
assert_eq!(for_uri_component("\x1F"), "%1F");
assert_eq!(for_uri_component("\x7F"), "%7F");
}
#[test]
fn uri_component_mixed() {
assert_eq!(
for_uri_component("key=hello world&foo=bar"),
"key%3Dhello%20world%26foo%3Dbar"
);
}
#[test]
fn uri_component_writer_variant() {
let mut out = String::new();
write_uri_component(&mut out, "a b").unwrap();
assert_eq!(out, "a%20b");
}
}