use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
const PATH_SEGMENT: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'%')
.add(b'/')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'\\')
.add(b'`')
.add(b'{')
.add(b'}');
pub fn encode_path_segment(s: &str) -> String {
utf8_percent_encode(s, PATH_SEGMENT).to_string()
}
pub fn encode_path_multi_segment(s: &str) -> String {
s.split('/')
.map(encode_path_segment)
.collect::<Vec<_>>()
.join("/")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encodes_slash_question_hash() {
assert_eq!(encode_path_segment("a/b"), "a%2Fb");
assert_eq!(encode_path_segment("a?b"), "a%3Fb");
assert_eq!(encode_path_segment("a#b"), "a%23b");
}
#[test]
fn leaves_uuid_untouched() {
let uuid = "8f3b2d1c-4a5e-4f6a-9b8c-1d2e3f4a5b6c";
assert_eq!(encode_path_segment(uuid), uuid);
}
#[test]
fn encodes_space_and_unicode() {
assert_eq!(encode_path_segment("hello world"), "hello%20world");
assert_eq!(encode_path_segment("café"), "caf%C3%A9");
}
#[test]
fn encodes_path_traversal() {
assert_eq!(encode_path_segment(".."), ".."); assert_eq!(encode_path_segment("../etc/passwd"), "..%2Fetc%2Fpasswd");
}
#[test]
fn multi_segment_preserves_slashes() {
assert_eq!(
encode_path_multi_segment("process-groups/root"),
"process-groups/root"
);
assert_eq!(
encode_path_multi_segment("processors/8f3b2d1c-4a5e-4f6a-9b8c-1d2e3f4a5b6c"),
"processors/8f3b2d1c-4a5e-4f6a-9b8c-1d2e3f4a5b6c"
);
}
#[test]
fn multi_segment_still_encodes_within_segments() {
assert_eq!(encode_path_multi_segment("a?b/c#d"), "a%3Fb/c%23d");
assert_eq!(encode_path_multi_segment("a b/c d"), "a%20b/c%20d");
}
}