use std::borrow::Cow;
pub(crate) fn encode_segment(segment: &str) -> Cow<'_, str> {
if !segment.contains([':', '%']) {
return Cow::Borrowed(segment);
}
let mut encoded = String::with_capacity(segment.len() + 8);
for c in segment.chars() {
match c {
'%' => encoded.push_str("%25"),
':' => encoded.push_str("%3A"),
other => encoded.push(other),
}
}
Cow::Owned(encoded)
}
#[cfg(feature = "redis-memory")]
pub(crate) fn escape_glob(literal: &str) -> Cow<'_, str> {
if !literal.contains(['*', '?', '[', ']', '\\']) {
return Cow::Borrowed(literal);
}
let mut escaped = String::with_capacity(literal.len() + 8);
for c in literal.chars() {
if matches!(c, '*' | '?' | '[' | ']' | '\\') {
escaped.push('\\');
}
escaped.push(c);
}
Cow::Owned(escaped)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_segments_are_unchanged() {
assert!(matches!(encode_segment("alice"), Cow::Borrowed("alice")));
}
#[test]
fn delimiter_and_escape_characters_are_encoded() {
assert_eq!(encode_segment("a:b"), "a%3Ab");
assert_eq!(encode_segment("100%"), "100%25");
assert_ne!(encode_segment("a%3Ab"), encode_segment("a:b"));
}
#[test]
fn encoded_segments_never_contain_the_delimiter() {
for raw in ["a:b", "::", "%3A:%25", "x"] {
assert!(!encode_segment(raw).contains(':'), "{raw} leaked a delimiter");
}
}
#[cfg(feature = "redis-memory")]
#[test]
fn glob_metacharacters_are_escaped() {
assert!(matches!(escape_glob("alice"), Cow::Borrowed("alice")));
assert_eq!(escape_glob("*"), "\\*");
assert_eq!(escape_glob(r"a?b[c]d\e"), r"a\?b\[c\]d\\e");
}
}