Skip to content

Commit 504505f

Browse files
committed
error on prefix-suffix conflicts
1 parent 654813a commit 504505f

File tree

6 files changed

+410
-63
lines changed

6 files changed

+410
-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: 219 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,51 @@ 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 child.prefix.len() <= suffix.len() {
223+
let (common, remaining) = suffix.split_at(child.prefix.len());
224+
if *common == *child.prefix && remaining == *b"/" {
225+
extra_trailing_slash = true;
226+
}
227+
}
228+
}
229+
230+
// If we are inserting a conflicting suffix, and there is a static prefix that
231+
// already leads to this route parameter, we have a prefix-suffix conflict.
232+
if !extra_trailing_slash && !matches!(*suffix, b"" | b"/") {
233+
if let Some(parent) = state.parent() {
234+
if parent.prefix_wild_child_in_segment() {
235+
return Err(InsertError::conflict(&route, common_remaining, parent));
236+
}
237+
}
163238
}
164239

165240
// Multiple parameters within the same segment, e.g. `/{foo}{bar}`.
@@ -168,19 +243,20 @@ impl<T> Node<T> {
168243
}
169244

170245
// If there is no matching suffix, create a new suffix node.
171-
let child = node.add_suffix_child(Node {
246+
let child = state.node_mut().add_suffix_child(Node {
172247
prefix: suffix.to_owned(),
173248
node_type: NodeType::Static,
174249
priority: 1,
175250
..Node::default()
176251
});
177-
node.node_type = NodeType::Param { suffix: true };
178-
node = &mut node.children[child];
252+
let has_suffix = !matches!(*suffix, b"" | b"/");
253+
state.node_mut().node_type = NodeType::Param { suffix: has_suffix };
254+
state = state.set_child(child);
179255

180256
// If this is the final route segment, insert the value.
181257
if terminator == remaining.len() {
182-
node.value = Some(UnsafeCell::new(val));
183-
node.remapping = remapping;
258+
state.node_mut().value = Some(UnsafeCell::new(val));
259+
state.node_mut().remapping = remapping;
184260
return Ok(());
185261
}
186262

@@ -190,40 +266,64 @@ impl<T> Node<T> {
190266

191267
// Create a static node unless we are inserting a parameter.
192268
if remaining[0] != b'{' || remaining.is_escaped(0) {
193-
let child = node.add_child(Node {
269+
let child = state.node_mut().add_child(Node {
194270
node_type: NodeType::Static,
195271
priority: 1,
196272
..Node::default()
197273
});
198-
node.indices.push(remaining[0]);
199-
node = &mut node.children[child];
274+
state.node_mut().indices.push(remaining[0]);
275+
state = state.set_child(child);
200276
}
201277

202278
// Insert the remaining route.
203-
let last = node.insert_route(remaining, val)?;
279+
let last = state.node_mut().insert_route(remaining, val)?;
204280
last.remapping = remapping;
205281
return Ok(());
206282
}
207283

208284
// 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] {
285+
for mut i in 0..state.node().indices.len() {
286+
if next == state.node().indices[i] {
211287
// Make sure not confuse the start of a wildcard with an escaped `{` or `}`.
212288
if matches!(next, b'{' | b'}') && !remaining.is_escaped(0) {
213289
continue;
214290
}
215291

216292
// Continue searching in the child.
217-
i = node.update_child_priority(i);
218-
node = &mut node.children[i];
293+
i = state.node_mut().update_child_priority(i);
294+
state = state.set_child(i);
219295
continue 'walk;
220296
}
221297
}
222298

223299
// We couldn't find a matching child.
224300
//
225301
// 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 {
302+
if (next != b'{' || remaining.is_escaped(0))
303+
&& state.node().node_type != NodeType::CatchAll
304+
{
305+
let node = state.node_mut();
306+
307+
let terminator = remaining
308+
.iter()
309+
.position(|&b| b == b'/')
310+
.unwrap_or(remaining.len());
311+
312+
if let Ok(Some(wildcard)) = find_wildcard(remaining.slice_until(terminator)) {
313+
// If we are inserting a parameter prefix and this node already has a parameter suffix,
314+
// we have a prefix-suffix conflict.
315+
if wildcard.start > 0 && node.suffix_wild_child_in_segment() {
316+
return Err(InsertError::conflict(&route, remaining, node));
317+
}
318+
319+
// Similarly, we are inserting a parameter suffix and this node already has a parameter
320+
// prefix, we have a prefix-suffix conflict.
321+
let suffix = remaining.slice_off(wildcard.end);
322+
if !matches!(*suffix, b"" | b"/") && node.prefix_wild_child_in_segment() {
323+
return Err(InsertError::conflict(&route, remaining, node));
324+
}
325+
}
326+
227327
node.indices.push(next);
228328
let child = node.add_child(Node::default());
229329
let child = node.update_child_priority(child);
@@ -237,34 +337,118 @@ impl<T> Node<T> {
237337
// We're trying to insert a wildcard.
238338
//
239339
// If this node already has a wildcard child, we have to make sure it matches.
240-
if node.wild_child {
340+
if state.node().wild_child {
241341
// Wildcards are always the last child.
242-
node = node.children.last_mut().unwrap();
243-
node.priority += 1;
342+
let wild_child = state.node().children.len() - 1;
343+
state = state.set_child(wild_child);
344+
state.node_mut().priority += 1;
244345

245346
// 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));
347+
if let Some(wildcard) = remaining.get(..state.node().prefix.len()) {
348+
if *wildcard != *state.node().prefix {
349+
return Err(InsertError::conflict(&route, remaining, state.node()));
249350
}
250351
}
251352

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

257382
// Continue with the wildcard node.
258383
continue 'walk;
259384
}
260385

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

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

0 commit comments

Comments
 (0)