use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::needle::NeedleCache;
use crate::node::Node;
use crate::state::StaticState;
#[derive(Clone, Debug)]
enum Condition {
EndsWith(Box<[u8]>),
Contains { needle: Box<[u8]>, id: usize },
}
impl Condition {
fn contains(needles: &mut BTreeMap<Box<[u8]>, usize>, prefix: &[u8]) -> Self {
let len = needles.len();
let id = *needles.entry(prefix.into()).or_insert(len);
Self::Contains {
needle: prefix.into(),
id,
}
}
}
#[derive(Clone, Debug)]
struct Group {
conditions: Box<[Condition]>,
}
impl Group {
fn single(condition: Condition) -> Self {
Self {
conditions: Box::new([condition]),
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Reachable {
groups: Box<[Group]>,
}
impl Reachable {
fn is_empty(&self) -> bool {
self.groups.is_empty()
}
pub(crate) fn check(&self, needles: &mut NeedleCache, path: &str, offset: usize) -> bool {
if self.groups.is_empty() {
return true;
}
let remaining = &path.as_bytes()[offset..];
self.groups.iter().any(|group| {
group.conditions.iter().all(|condition| match condition {
Condition::EndsWith(suffix) => {
remaining.len() >= suffix.len()
&& suffix
.iter()
.rev()
.zip(remaining.iter().rev())
.all(|(a, b)| a == b)
}
Condition::Contains { needle, id } => needles
.rightmost(*id, needle, path.as_bytes())
.is_some_and(|position| position >= offset),
})
})
}
pub(crate) fn compute<S, T>(
node: &Node<S, T>,
needles: &mut BTreeMap<Box<[u8]>, usize>,
) -> Self {
if node.data.is_some() || node.end_wildcard.is_some() {
return Self::default();
}
let mut groups = Vec::new();
for child in &node.static_children {
let mut prefix = child.state.prefix.to_vec();
if !Self::collect_static(child, &mut prefix, &mut groups, needles) {
return Self::default();
}
}
for child in node
.dynamic_children
.iter()
.map(|child| &child.reachable)
.chain(node.wildcard_children.iter().map(|child| &child.reachable))
{
if child.is_empty() {
return Self::default();
}
groups.extend(child.groups.iter().cloned());
}
if groups.is_empty() {
return Self::default();
}
Self {
groups: groups.into_boxed_slice(),
}
}
fn collect_static<T>(
node: &Node<StaticState, T>,
prefix: &mut Vec<u8>,
groups: &mut Vec<Group>,
needles: &mut BTreeMap<Box<[u8]>, usize>,
) -> bool {
let has_data = node.data.is_some();
let has_params = !node.dynamic_children.is_empty()
|| !node.wildcard_children.is_empty()
|| node.end_wildcard.is_some();
if has_params && prefix.len() <= 1 {
return false;
}
if has_data && !has_params && node.static_children.is_empty() {
groups.push(Group::single(Condition::EndsWith(
prefix.clone().into_boxed_slice(),
)));
return true;
}
if has_params {
let contains = Condition::contains(needles, prefix);
let mut deeper_groups = Vec::new();
let mut has_unconstrained = node.end_wildcard.is_some();
for child_reachable in node
.dynamic_children
.iter()
.map(|child| &child.reachable)
.chain(node.wildcard_children.iter().map(|child| &child.reachable))
{
if child_reachable.is_empty() {
has_unconstrained = true;
} else {
for group in &*child_reachable.groups {
deeper_groups.push(group.conditions.to_vec());
}
}
}
if has_unconstrained || deeper_groups.is_empty() {
groups.push(Group::single(contains));
} else {
for mut deeper in deeper_groups {
let already_contains = deeper.iter().any(|condition| {
matches!(condition, Condition::Contains { needle, .. } if **needle == *prefix)
});
if !already_contains {
deeper.insert(0, contains.clone());
}
groups.push(Group {
conditions: deeper.into_boxed_slice(),
});
}
}
}
if has_data && (has_params || !node.static_children.is_empty()) {
groups.push(Group::single(Condition::contains(needles, prefix)));
}
for child in &node.static_children {
let start = prefix.len();
prefix.extend_from_slice(&child.state.prefix);
if !Self::collect_static(child, prefix, groups, needles) {
return false;
}
prefix.truncate(start);
}
true
}
}