pub(crate) fn path_prefix_matches(path: &str, prefix: &str) -> bool {
let trimmed = prefix.strip_suffix('/').unwrap_or(prefix);
if trimmed.is_empty() {
return true;
}
if path == trimmed {
return true;
}
path.starts_with(trimmed) && path.as_bytes().get(trimmed.len()) == Some(&b'/')
}
pub(crate) fn path_prefix_specificity(prefix: &str) -> usize {
let trimmed = prefix.strip_suffix('/').unwrap_or(prefix);
if trimmed.is_empty() { 1 } else { trimmed.len() }
}
#[cfg(test)]
#[expect(clippy::allow_attributes, reason = "blanket test suppressions")]
#[allow(clippy::unwrap_used, reason = "tests")]
mod tests {
use super::*;
#[test]
fn root_matches_everything() {
assert!(path_prefix_matches("/anything", "/"));
assert!(path_prefix_matches("/", "/"));
}
#[test]
fn empty_prefix_matches_everything() {
assert!(path_prefix_matches("/foo", ""));
assert!(path_prefix_matches("/", ""));
}
#[test]
fn exact_match() {
assert!(path_prefix_matches("/api", "/api"));
}
#[test]
fn with_trailing_slash_on_path() {
assert!(path_prefix_matches("/api/", "/api"));
}
#[test]
fn subpath_match() {
assert!(path_prefix_matches("/api/v1", "/api"));
}
#[test]
fn no_segment_boundary_rejected() {
assert!(!path_prefix_matches("/apikeys", "/api"));
}
#[test]
fn prefix_with_trailing_slash_equivalent() {
assert!(path_prefix_matches("/api/v1", "/api/"));
assert!(path_prefix_matches("/api", "/api/"));
}
#[test]
fn specificity_trims_trailing_slash() {
assert_eq!(path_prefix_specificity("/api"), path_prefix_specificity("/api/"));
}
#[test]
fn specificity_root() {
assert_eq!(path_prefix_specificity("/"), 1);
assert_eq!(path_prefix_specificity(""), 1);
}
#[test]
fn specificity_value() {
assert_eq!(path_prefix_specificity("/api"), 4);
assert_eq!(path_prefix_specificity("/api/v1"), 7);
}
#[test]
fn double_slash_prefix() {
assert!(!path_prefix_matches("/foo", "//"), "/foo must not match // prefix");
assert!(path_prefix_matches("//bar", "//"), "//bar should match // prefix");
}
}