use std::collections::HashMap;
#[derive(Clone)]
#[doc(hidden)]
pub enum Segment {
Literal(String),
Param(String),
Wildcard(String),
}
#[doc(hidden)]
pub fn braced(segment: &str) -> Option<&str> {
let inner = segment.strip_prefix('{')?.strip_suffix('}')?;
let valid = !inner.is_empty() && !inner.starts_with('{') && !inner.contains(['{', '}', '/']);
valid.then_some(inner)
}
#[doc(hidden)]
pub fn parse_pattern(pattern: &str) -> Vec<Segment> {
if pattern == "/" {
return vec![];
}
pattern
.split('/')
.filter(|s| !s.is_empty())
.map(|seg| {
if let Some(name) = seg.strip_prefix(':') {
Segment::Param(name.to_string())
} else if let Some(name) = seg.strip_prefix('*') {
Segment::Wildcard(name.to_string())
} else if let Some(inner) = braced(seg) {
match inner.strip_prefix('*') {
Some(name) => Segment::Wildcard(name.to_string()),
None => Segment::Param(inner.to_string()),
}
} else {
Segment::Literal(seg.replace("{{", "{").replace("}}", "}"))
}
})
.collect()
}
#[doc(hidden)]
pub fn try_match(pattern: &[Segment], path: &[&str]) -> Option<HashMap<String, String>> {
let mut params = HashMap::new();
let mut pi = 0;
for (si, seg) in pattern.iter().enumerate() {
match seg {
Segment::Literal(lit) => {
if pi >= path.len() || path[pi] != lit.as_str() {
return None;
}
pi += 1;
}
Segment::Param(name) => {
if pi >= path.len() {
return None;
}
params.insert(name.clone(), path[pi].to_string());
pi += 1;
}
Segment::Wildcard(name) => {
if si != pattern.len() - 1 {
return None; }
params.insert(name.clone(), path[pi..].join("/"));
pi = path.len();
}
}
}
if pi == path.len() { Some(params) } else { None }
}
#[doc(hidden)]
pub fn segments_to_pattern(segs: &[Segment]) -> String {
if segs.is_empty() {
return "/".to_string();
}
let parts: Vec<String> = segs
.iter()
.map(|s| match s {
Segment::Literal(l) => l.clone(),
Segment::Param(n) => format!(":{}", n),
Segment::Wildcard(n) => format!("*{}", n),
})
.collect();
format!("/{}", parts.join("/"))
}
#[doc(hidden)]
pub fn equivalent(a: &[Segment], b: &[Segment]) -> bool {
a.len() == b.len()
&& a.iter().zip(b).all(|pair| match pair {
(Segment::Literal(x), Segment::Literal(y)) => x == y,
(Segment::Param(_), Segment::Param(_)) | (Segment::Wildcard(_), Segment::Wildcard(_)) => true,
_ => false,
})
}
#[doc(hidden)]
pub fn covers(earlier: &[Segment], later: &[Segment]) -> bool {
if never_matches(earlier) || never_matches(later) {
return false;
}
for (i, seg) in earlier.iter().enumerate() {
match (seg, later.get(i)) {
(Segment::Wildcard(_), _) => return true,
(_, None) => return false,
(Segment::Literal(x), Some(Segment::Literal(y))) => {
if x != y {
return false;
}
}
(Segment::Literal(_), Some(_)) => return false,
(Segment::Param(_), Some(Segment::Wildcard(_))) => return false,
(Segment::Param(_), Some(_)) => {}
}
}
earlier.len() == later.len()
}
#[doc(hidden)]
pub fn never_matches(segs: &[Segment]) -> bool {
segs.iter().rev().skip(1).any(|s| matches!(s, Segment::Wildcard(_)))
}