Skip to content

Commit 419ada6

Browse files
committed
error on prefix-suffix conflicts
1 parent 654813a commit 419ada6

File tree

6 files changed

+411
-63
lines changed

6 files changed

+411
-63
lines changed

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl InsertError {
7979
route.append(&current.prefix);
8080
}
8181

82-
// Add the prefixes of any conflicting children.
82+
// Add the prefixes of the first conflicting child.
8383
let mut child = current.children.first();
8484
while let Some(node) = child {
8585
route.append(&node.prefix);

src/tree.rs

Lines changed: 220 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct Node<T> {
3434
// The value stored at this node.
3535
//
3636
// See `Node::at` for why an `UnsafeCell` is necessary.
37-
value: Option<UnsafeCell<T>>,
37+
pub(crate) value: Option<UnsafeCell<T>>,
3838

3939
// A parameter name remapping, stored at nodes that hold values.
4040
pub(crate) remapping: ParamRemapping,
@@ -71,6 +71,52 @@ pub(crate) enum NodeType {
7171
unsafe impl<T: Send> Send for Node<T> {}
7272
unsafe impl<T: Sync> Sync for Node<T> {}
7373

74+
/// Tracks the current node and its parent during insertion.
75+
struct InsertState<'node, T> {
76+
parent: &'node mut Node<T>,
77+
child: Option<usize>,
78+
}
79+
80+
impl<'node, T> InsertState<'node, T> {
81+
/// Returns a reference to the parent node for the traversal.
82+
fn parent(&self) -> Option<&Node<T>> {
83+
match self.child {
84+
None => None,
85+
Some(_) => Some(self.parent),
86+
}
87+
}
88+
89+
/// Returns a reference to the current node in the traversal.
90+
fn node(&self) -> &Node<T> {
91+
match self.child {
92+
None => self.parent,
93+
Some(i) => &self.parent.children[i],
94+
}
95+
}
96+
97+
/// Returns a mutable reference to the current node in the traversal.
98+
fn node_mut(&mut self) -> &mut Node<T> {
99+
match self.child {
100+
None => self.parent,
101+
Some(i) => &mut self.parent.children[i],
102+
}
103+
}
104+
105+
/// Move the current node to its i'th child.
106+
fn set_child(self, i: usize) -> InsertState<'node, T> {
107+
match self.child {
108+
None => InsertState {
109+
parent: self.parent,
110+
child: Some(i),
111+
},
112+
Some(prev) => InsertState {
113+
parent: &mut self.parent.children[prev],
114+
child: Some(i),
115+
},
116+
}
117+
}
118+
}
119+
74120
impl<T> Node<T> {
75121
// Insert a route into the tree.
76122
pub fn insert(&mut self, route: String, val: T) -> Result<(), InsertError> {
@@ -88,21 +134,27 @@ impl<T> Node<T> {
88134
return Ok(());
89135
}
90136

91-
let mut node = self;
137+
let mut state = InsertState {
138+
parent: self,
139+
child: None,
140+
};
141+
92142
'walk: loop {
93143
// Find the common prefix between the route and the current node.
94-
let len = min(remaining.len(), node.prefix.len());
144+
let len = min(remaining.len(), state.node().prefix.len());
95145
let common_prefix = (0..len)
96146
.find(|&i| {
97-
remaining[i] != node.prefix[i]
147+
remaining[i] != state.node().prefix[i]
98148
// Make sure not confuse the start of a wildcard with an escaped `{`.
99-
|| remaining.is_escaped(i) != node.prefix.is_escaped(i)
149+
|| remaining.is_escaped(i) != state.node().prefix.is_escaped(i)
100150
})
101151
.unwrap_or(len);
102152

103153
// If this node has a longer prefix than we need, we have to fork and extract the
104154
// common prefix into a shared parent.
105-
if node.prefix.len() > common_prefix {
155+
if state.node().prefix.len() > common_prefix {
156+
let node = state.node_mut();
157+
106158
// Move the non-matching suffix into a child node.
107159
let suffix = node.prefix.as_ref().slice_off(common_prefix).to_owned();
108160
let child = Node {
@@ -125,6 +177,8 @@ impl<T> Node<T> {
125177
}
126178

127179
if remaining.len() == common_prefix {
180+
let node = state.node_mut();
181+
128182
// This node must not already contain a value.
129183
if node.value.is_some() {
130184
return Err(InsertError::conflict(&route, remaining, node));
@@ -136,30 +190,50 @@ impl<T> Node<T> {
136190
return Ok(());
137191
}
138192

193+
let common_remaining = remaining;
194+
139195
// Otherwise, the route has a remaining non-matching suffix.
140196
//
141197
// We have to search deeper.
142198
remaining = remaining.slice_off(common_prefix);
143199
let next = remaining[0];
144200

145-
// For parameters with a suffix, we have to find the matching suffix or
146-
// create a new child node.
147-
if matches!(node.node_type, NodeType::Param { .. }) {
201+
// For parameters with a suffix, we have to find the matching suffix or create a new child node.
202+
if matches!(state.node().node_type, NodeType::Param { .. }) {
148203
let terminator = remaining
149204
.iter()
150205
.position(|&b| b == b'/')
206+
// Include the '/' in the suffix.
151207
.map(|b| b + 1)
152208
.unwrap_or(remaining.len());
153209

154210
let suffix = remaining.slice_until(terminator);
155211

156-
for (i, child) in node.children.iter().enumerate() {
212+
let mut extra_trailing_slash = false;
213+
for (i, child) in state.node().children.iter().enumerate() {
157214
// Find a matching suffix.
158215
if *child.prefix == **suffix {
159-
node = &mut node.children[i];
160-
node.priority += 1;
216+
state.node_mut().priority += 1;
217+
state = state.set_child(i);
161218
continue 'walk;
162219
}
220+
221+
// The suffix matches except for an extra trailing slash.
222+
if let Some((common, remaining)) = suffix.split_at_checked(child.prefix.len()) {
223+
if *common == *child.prefix && remaining == *b"/" {
224+
extra_trailing_slash = true;
225+
}
226+
}
227+
}
228+
229+
// If we are inserting a conflicting suffix, and there is a static prefix that already leads to
230+
// this route parameter, we have a prefix-suffix conflict.
231+
if !extra_trailing_slash && !matches!(*suffix, b"" | b"/") {
232+
if let Some(parent) = state.parent() {
233+
if parent.prefix_wild_child_in_segment() {
234+
return Err(InsertError::conflict(&route, common_remaining, parent));
235+
}
236+
}
163237
}
164238

165239
// Multiple parameters within the same segment, e.g. `/{foo}{bar}`.
@@ -168,19 +242,20 @@ impl<T> Node<T> {
168242
}
169243

170244
// If there is no matching suffix, create a new suffix node.
171-
let child = node.add_suffix_child(Node {
245+
let child = state.node_mut().add_suffix_child(Node {
172246
prefix: suffix.to_owned(),
173247
node_type: NodeType::Static,
174248
priority: 1,
175249
..Node::default()
176250
});
177-
node.node_type = NodeType::Param { suffix: true };
178-
node = &mut node.children[child];
251+
let has_suffix = !matches!(*suffix, b"" | b"/");
252+
state.node_mut().node_type = NodeType::Param { suffix: has_suffix };
253+
state = state.set_child(child);
179254

180255
// If this is the final route segment, insert the value.
181256
if terminator == remaining.len() {
182-
node.value = Some(UnsafeCell::new(val));
183-
node.remapping = remapping;
257+
state.node_mut().value = Some(UnsafeCell::new(val));
258+
state.node_mut().remapping = remapping;
184259
return Ok(());
185260
}
186261

@@ -190,40 +265,66 @@ impl<T> Node<T> {
190265

191266
// Create a static node unless we are inserting a parameter.
192267
if remaining[0] != b'{' || remaining.is_escaped(0) {
193-
let child = node.add_child(Node {
268+
let child = state.node_mut().add_child(Node {
194269
node_type: NodeType::Static,
195270
priority: 1,
196271
..Node::default()
197272
});
198-
node.indices.push(remaining[0]);
199-
node = &mut node.children[child];
273+
state.node_mut().indices.push(remaining[0]);
274+
state = state.set_child(child);
200275
}
201276

202277
// Insert the remaining route.
203-
let last = node.insert_route(remaining, val)?;
278+
let last = state.node_mut().insert_route(remaining, val)?;
204279
last.remapping = remapping;
205280
return Ok(());
206281
}
207282

208283
// Find a child node that matches the next character in the route.
209-
for mut i in 0..node.indices.len() {
210-
if next == node.indices[i] {
284+
for mut i in 0..state.node().indices.len() {
285+
if next == state.node().indices[i] {
211286
// Make sure not confuse the start of a wildcard with an escaped `{` or `}`.
212287
if matches!(next, b'{' | b'}') && !remaining.is_escaped(0) {
213288
continue;
214289
}
215290

216291
// Continue searching in the child.
217-
i = node.update_child_priority(i);
218-
node = &mut node.children[i];
292+
i = state.node_mut().update_child_priority(i);
293+
state = state.set_child(i);
219294
continue 'walk;
220295
}
221296
}
222297

223298
// We couldn't find a matching child.
224299
//
225300
// If we're not inserting a wildcard we have to create a static child.
226-
if (next != b'{' || remaining.is_escaped(0)) && node.node_type != NodeType::CatchAll {
301+
if (next != b'{' || remaining.is_escaped(0))
302+
&& state.node().node_type != NodeType::CatchAll
303+
{
304+
let node = state.node_mut();
305+
306+
let terminator = remaining
307+
.iter()
308+
.position(|&b| b == b'/')
309+
.unwrap_or(remaining.len());
310+
311+
if let Ok(Some(wildcard)) = find_wildcard(remaining.slice_until(terminator)) {
312+
// If we are inserting a parameter prefix and this node already has a parameter suffix, we
313+
// have a prefix-suffix conflict.
314+
if wildcard.start > 0 && node.suffix_wild_child_in_segment() {
315+
return Err(InsertError::conflict(&route, remaining, node));
316+
}
317+
318+
// Similarly, we are inserting a parameter suffix and this node already has a parameter prefix, we
319+
// have a prefix-suffix conflict.
320+
let suffix = remaining.slice_off(wildcard.end);
321+
if !matches!(*suffix, b"" | b"/") {
322+
if node.prefix_wild_child_in_segment() {
323+
return Err(InsertError::conflict(&route, remaining, node));
324+
}
325+
}
326+
}
327+
227328
node.indices.push(next);
228329
let child = node.add_child(Node::default());
229330
let child = node.update_child_priority(child);
@@ -237,34 +338,118 @@ impl<T> Node<T> {
237338
// We're trying to insert a wildcard.
238339
//
239340
// If this node already has a wildcard child, we have to make sure it matches.
240-
if node.wild_child {
341+
if state.node().wild_child {
241342
// Wildcards are always the last child.
242-
node = node.children.last_mut().unwrap();
243-
node.priority += 1;
343+
let wild_child = state.node().children.len() - 1;
344+
state = state.set_child(wild_child);
345+
state.node_mut().priority += 1;
244346

245347
// Make sure the route parameter matches.
246-
if let Some(wildcard) = remaining.get(..node.prefix.len()) {
247-
if *wildcard != *node.prefix {
248-
return Err(InsertError::conflict(&route, remaining, node));
348+
if let Some(wildcard) = remaining.get(..state.node().prefix.len()) {
349+
if *wildcard != *state.node().prefix {
350+
return Err(InsertError::conflict(&route, remaining, state.node()));
249351
}
250352
}
251353

252354
// Catch-all routes cannot have children.
253-
if node.node_type == NodeType::CatchAll {
254-
return Err(InsertError::conflict(&route, remaining, node));
355+
if state.node().node_type == NodeType::CatchAll {
356+
return Err(InsertError::conflict(&route, remaining, state.node()));
357+
}
358+
359+
if let Some(parent) = state.parent() {
360+
// If there is a route with both a prefix and a suffix, and we are inserting a route with a
361+
// matching prefix but _without_ a suffix, we have a prefix-suffix conflict.
362+
if !parent.prefix.ends_with(b"/")
363+
&& !state.node().prefix.starts_with(b"/")
364+
&& matches!(state.node().node_type, NodeType::Param { suffix: true })
365+
{
366+
let terminator = remaining
367+
.iter()
368+
.position(|&b| b == b'/')
369+
// Include the '/' in the suffix.
370+
.map(|b| b + 1)
371+
.unwrap_or(remaining.len());
372+
373+
if let Ok(Some(wildcard)) = find_wildcard(remaining.slice_until(terminator))
374+
{
375+
let suffix = remaining.slice_off(wildcard.end);
376+
if matches!(*suffix, b"" | b"/") {
377+
return Err(InsertError::conflict(&route, remaining, parent));
378+
}
379+
}
380+
}
255381
}
256382

257383
// Continue with the wildcard node.
258384
continue 'walk;
259385
}
260386

387+
if let Ok(Some(wildcard)) = find_wildcard(remaining) {
388+
let node = state.node();
389+
let suffix = remaining.slice_off(wildcard.end);
390+
391+
// If we are inserting a suffix and there is a static prefix that already leads to this
392+
// route parameter, we have a prefix-suffix conflict.
393+
if !matches!(*suffix, b"" | b"/") && node.prefix_wild_child_in_segment() {
394+
return Err(InsertError::conflict(&route, remaining, node));
395+
}
396+
397+
// Similarly, if we are inserting a prefix, and there is a route that leads to this parameter that includes a
398+
// suffix, we have a prefix-suffix conflicts.
399+
if common_remaining[common_prefix - 1] != b'/'
400+
&& node.suffix_wild_child_in_segment()
401+
{
402+
return Err(InsertError::conflict(&route, remaining, node));
403+
}
404+
}
405+
261406
// Otherwise, create a new node for the wildcard and insert the route.
262-
let last = node.insert_route(remaining, val)?;
407+
let last = state.node_mut().insert_route(remaining, val)?;
263408
last.remapping = remapping;
264409
return Ok(());
265410
}
266411
}
267412

413+
/// Returns `true` if there is a wildcard node that contains a prefix within the current route segment,
414+
/// i.e. before the next trailing slash
415+
fn prefix_wild_child_in_segment(&self) -> bool {
416+
if self.prefix.ends_with(b"/") {
417+
self.children.iter().any(Node::prefix_wild_child_in_segment)
418+
} else {
419+
self.children.iter().any(Node::wild_child_in_segment)
420+
}
421+
}
422+
423+
/// Returns `true` if there is a wildcard node within the current route segment, i.e. before the
424+
/// next trailing slash.
425+
fn wild_child_in_segment(&self) -> bool {
426+
if self.prefix.contains(&b'/') {
427+
return false;
428+
}
429+
430+
if matches!(self.node_type, NodeType::Param { .. }) {
431+
return true;
432+
}
433+
434+
self.children.iter().any(Node::wild_child_in_segment)
435+
}
436+
437+
/// Returns `true` if there is a wildcard parameter node that contains a suffix within the current route
438+
/// segment, i.e. before a trailing slash.
439+
fn suffix_wild_child_in_segment(&self) -> bool {
440+
if matches!(self.node_type, NodeType::Param { suffix: true }) {
441+
return true;
442+
}
443+
444+
self.children.iter().any(|child| {
445+
if child.prefix.contains(&b'/') {
446+
return false;
447+
}
448+
449+
child.suffix_wild_child_in_segment()
450+
})
451+
}
452+
268453
// Insert a route at this node.
269454
//
270455
// If the route starts with a wildcard, a child node will be created for the parameter

0 commit comments

Comments
 (0)