Skip to content

Commit 8892867

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

File tree

4 files changed

+376
-64
lines changed

4 files changed

+376
-64
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: 197 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,47 @@ pub(crate) enum NodeType {
7171
unsafe impl<T: Send> Send for Node<T> {}
7272
unsafe impl<T: Sync> Sync for Node<T> {}
7373

74+
struct InsertState<'node, T> {
75+
parent: &'node mut Node<T>,
76+
child: Option<usize>,
77+
}
78+
79+
impl<'node, T> InsertState<'node, T> {
80+
fn node_mut(&mut self) -> &mut Node<T> {
81+
match self.child {
82+
None => self.parent,
83+
Some(i) => &mut self.parent.children[i],
84+
}
85+
}
86+
87+
fn parent(&self) -> Option<&Node<T>> {
88+
match self.child {
89+
None => None,
90+
Some(_) => Some(self.parent),
91+
}
92+
}
93+
94+
fn node(&self) -> &Node<T> {
95+
match self.child {
96+
None => self.parent,
97+
Some(i) => &self.parent.children[i],
98+
}
99+
}
100+
101+
fn set_child(self, i: usize) -> InsertState<'node, T> {
102+
match self.child {
103+
None => InsertState {
104+
parent: self.parent,
105+
child: Some(i),
106+
},
107+
Some(prev) => InsertState {
108+
parent: &mut self.parent.children[prev],
109+
child: Some(i),
110+
},
111+
}
112+
}
113+
}
114+
74115
impl<T> Node<T> {
75116
// Insert a route into the tree.
76117
pub fn insert(&mut self, route: String, val: T) -> Result<(), InsertError> {
@@ -88,21 +129,27 @@ impl<T> Node<T> {
88129
return Ok(());
89130
}
90131

91-
let mut node = self;
132+
let mut state = InsertState {
133+
parent: self,
134+
child: None,
135+
};
136+
92137
'walk: loop {
93138
// Find the common prefix between the route and the current node.
94-
let len = min(remaining.len(), node.prefix.len());
139+
let len = min(remaining.len(), state.node().prefix.len());
95140
let common_prefix = (0..len)
96141
.find(|&i| {
97-
remaining[i] != node.prefix[i]
142+
remaining[i] != state.node().prefix[i]
98143
// Make sure not confuse the start of a wildcard with an escaped `{`.
99-
|| remaining.is_escaped(i) != node.prefix.is_escaped(i)
144+
|| remaining.is_escaped(i) != state.node().prefix.is_escaped(i)
100145
})
101146
.unwrap_or(len);
102147

103148
// If this node has a longer prefix than we need, we have to fork and extract the
104149
// common prefix into a shared parent.
105-
if node.prefix.len() > common_prefix {
150+
if state.node().prefix.len() > common_prefix {
151+
let node = state.node_mut();
152+
106153
// Move the non-matching suffix into a child node.
107154
let suffix = node.prefix.as_ref().slice_off(common_prefix).to_owned();
108155
let child = Node {
@@ -125,6 +172,8 @@ impl<T> Node<T> {
125172
}
126173

127174
if remaining.len() == common_prefix {
175+
let node = state.node_mut();
176+
128177
// This node must not already contain a value.
129178
if node.value.is_some() {
130179
return Err(InsertError::conflict(&route, remaining, node));
@@ -136,30 +185,58 @@ impl<T> Node<T> {
136185
return Ok(());
137186
}
138187

188+
let common_remaining = remaining;
189+
139190
// Otherwise, the route has a remaining non-matching suffix.
140191
//
141192
// We have to search deeper.
142193
remaining = remaining.slice_off(common_prefix);
143194
let next = remaining[0];
144195

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 { .. }) {
196+
// For parameters with a suffix, we have to find the matching suffix or create a new child node.
197+
if matches!(state.node().node_type, NodeType::Param { .. }) {
148198
let terminator = remaining
149199
.iter()
150200
.position(|&b| b == b'/')
201+
// Include the '/' in the suffix.
151202
.map(|b| b + 1)
152203
.unwrap_or(remaining.len());
153204

154205
let suffix = remaining.slice_until(terminator);
155206

156-
for (i, child) in node.children.iter().enumerate() {
207+
let mut extra_trailing_slash = false;
208+
for (i, child) in state.node().children.iter().enumerate() {
157209
// Find a matching suffix.
158210
if *child.prefix == **suffix {
159-
node = &mut node.children[i];
160-
node.priority += 1;
211+
state.node_mut().priority += 1;
212+
state = state.set_child(i);
161213
continue 'walk;
162214
}
215+
216+
// Matches except for an extra trailing slash.
217+
if let Some((common, remaining)) = suffix.split_at_checked(child.prefix.len()) {
218+
if *common == *child.prefix && remaining == *b"/" {
219+
extra_trailing_slash = true;
220+
}
221+
}
222+
}
223+
224+
// If we are inserting a conflicting suffix, and there is a static prefix that already leads to
225+
// this route parameter, we have a prefix-suffix conflict.
226+
if !extra_trailing_slash && !matches!(*suffix, b"" | b"/") {
227+
if let Some(parent) = state.parent() {
228+
// TODO: Combine these two conditions.
229+
if parent.wild_child_in_segment() {
230+
return Err(InsertError::conflict(&route, common_remaining, parent));
231+
}
232+
for child in &parent.children {
233+
if matches!(child.node_type, NodeType::Static)
234+
&& child.wild_child_in_segment()
235+
{
236+
return Err(InsertError::conflict(&route, common_remaining, child));
237+
}
238+
}
239+
}
163240
}
164241

165242
// Multiple parameters within the same segment, e.g. `/{foo}{bar}`.
@@ -168,19 +245,20 @@ impl<T> Node<T> {
168245
}
169246

170247
// If there is no matching suffix, create a new suffix node.
171-
let child = node.add_suffix_child(Node {
248+
let child = state.node_mut().add_suffix_child(Node {
172249
prefix: suffix.to_owned(),
173250
node_type: NodeType::Static,
174251
priority: 1,
175252
..Node::default()
176253
});
177-
node.node_type = NodeType::Param { suffix: true };
178-
node = &mut node.children[child];
254+
let has_suffix = !matches!(*suffix, b"" | b"/");
255+
state.node_mut().node_type = NodeType::Param { suffix: has_suffix };
256+
state = state.set_child(child);
179257

180258
// If this is the final route segment, insert the value.
181259
if terminator == remaining.len() {
182-
node.value = Some(UnsafeCell::new(val));
183-
node.remapping = remapping;
260+
state.node_mut().value = Some(UnsafeCell::new(val));
261+
state.node_mut().remapping = remapping;
184262
return Ok(());
185263
}
186264

@@ -190,40 +268,68 @@ impl<T> Node<T> {
190268

191269
// Create a static node unless we are inserting a parameter.
192270
if remaining[0] != b'{' || remaining.is_escaped(0) {
193-
let child = node.add_child(Node {
271+
let child = state.node_mut().add_child(Node {
194272
node_type: NodeType::Static,
195273
priority: 1,
196274
..Node::default()
197275
});
198-
node.indices.push(remaining[0]);
199-
node = &mut node.children[child];
276+
state.node_mut().indices.push(remaining[0]);
277+
state = state.set_child(child);
200278
}
201279

202280
// Insert the remaining route.
203-
let last = node.insert_route(remaining, val)?;
281+
let last = state.node_mut().insert_route(remaining, val)?;
204282
last.remapping = remapping;
205283
return Ok(());
206284
}
207285

208286
// 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] {
287+
for mut i in 0..state.node().indices.len() {
288+
if next == state.node().indices[i] {
211289
// Make sure not confuse the start of a wildcard with an escaped `{` or `}`.
212290
if matches!(next, b'{' | b'}') && !remaining.is_escaped(0) {
213291
continue;
214292
}
215293

216294
// Continue searching in the child.
217-
i = node.update_child_priority(i);
218-
node = &mut node.children[i];
295+
i = state.node_mut().update_child_priority(i);
296+
state = state.set_child(i);
219297
continue 'walk;
220298
}
221299
}
222300

223301
// We couldn't find a matching child.
224302
//
225303
// 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 {
304+
if (next != b'{' || remaining.is_escaped(0))
305+
&& state.node().node_type != NodeType::CatchAll
306+
{
307+
let node = state.node_mut();
308+
309+
let terminator = remaining
310+
.iter()
311+
.position(|&b| b == b'/')
312+
.unwrap_or(remaining.len());
313+
314+
if let Ok(Some(wildcard)) = find_wildcard(remaining.slice_until(terminator)) {
315+
// If we are inserting a parameter prefix and this node already has a parameter suffix, we
316+
// have a prefix-suffix conflict.
317+
if wildcard.start > 0 && node.wild_child {
318+
let wild_child = node.children.last().unwrap();
319+
320+
if matches!(wild_child.node_type, NodeType::Param { suffix: true }) {
321+
return Err(InsertError::conflict(&route, remaining, wild_child));
322+
}
323+
}
324+
325+
// Similarly, we are inserting a parameter suffix and this node already has a parameter prefix, we
326+
// have a prefix-suffix conflict.
327+
let suffix = remaining.slice_off(wildcard.end);
328+
if !matches!(*suffix, b"" | b"/") && node.wild_child_in_segment() {
329+
return Err(InsertError::conflict(&route, remaining, node));
330+
}
331+
}
332+
227333
node.indices.push(next);
228334
let child = node.add_child(Node::default());
229335
let child = node.update_child_priority(child);
@@ -237,34 +343,90 @@ impl<T> Node<T> {
237343
// We're trying to insert a wildcard.
238344
//
239345
// If this node already has a wildcard child, we have to make sure it matches.
240-
if node.wild_child {
346+
if state.node().wild_child {
241347
// Wildcards are always the last child.
242-
node = node.children.last_mut().unwrap();
243-
node.priority += 1;
348+
let wild_child = state.node().children.len() - 1;
349+
state = state.set_child(wild_child);
350+
state.node_mut().priority += 1;
244351

245352
// 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));
353+
if let Some(wildcard) = remaining.get(..state.node().prefix.len()) {
354+
if *wildcard != *state.node().prefix {
355+
return Err(InsertError::conflict(&route, remaining, state.node()));
249356
}
250357
}
251358

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

257388
// Continue with the wildcard node.
258389
continue 'walk;
259390
}
260391

392+
if let Ok(Some(wildcard)) = find_wildcard(remaining) {
393+
let node = state.node();
394+
let suffix = remaining.slice_off(wildcard.end);
395+
396+
// If we are inserting a suffix and there is a static prefix that already leads to this
397+
// route parameter, we have a prefix-suffix conflict.
398+
if !matches!(*suffix, b"" | b"/") {
399+
for child in &node.children {
400+
if matches!(child.node_type, NodeType::Static)
401+
&& child.wild_child_in_segment()
402+
{
403+
return Err(InsertError::conflict(&route, remaining, child));
404+
}
405+
}
406+
}
407+
}
408+
261409
// Otherwise, create a new node for the wildcard and insert the route.
262-
let last = node.insert_route(remaining, val)?;
410+
let last = state.node_mut().insert_route(remaining, val)?;
263411
last.remapping = remapping;
264412
return Ok(());
265413
}
266414
}
267415

416+
/// Returns `true` if there is a wildcard parameter node within the current route segment,
417+
/// i.e. before a trailing slash.
418+
fn wild_child_in_segment(&self) -> bool {
419+
if self.prefix.contains(&b'/') {
420+
return false;
421+
}
422+
423+
if self.wild_child {
424+
return true;
425+
}
426+
427+
self.children.iter().any(Node::wild_child_in_segment)
428+
}
429+
268430
// Insert a route at this node.
269431
//
270432
// If the route starts with a wildcard, a child node will be created for the parameter

0 commit comments

Comments
 (0)