Skip to content

Commit 390c567

Browse files
committed
support removal of suffixed route parameters
1 parent 6960201 commit 390c567

File tree

2 files changed

+77
-32
lines changed

2 files changed

+77
-32
lines changed

src/tree.rs

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ impl<T> Node<T> {
145145
// For parameters with a suffix, we have to find the matching suffix or
146146
// create a new child node.
147147
if matches!(node.node_type, NodeType::Param { .. }) {
148-
let rest = remaining
148+
let terminator = remaining
149149
.iter()
150150
.position(|&b| b == b'/')
151151
.map(|b| b + 1)
152152
.unwrap_or(remaining.len());
153153

154-
let suffix = remaining.slice_until(rest);
154+
let suffix = remaining.slice_until(terminator);
155155

156156
for (i, child) in node.children.iter().enumerate() {
157157
// Find a matching suffix.
@@ -178,15 +178,15 @@ impl<T> Node<T> {
178178
node = &mut node.children[child];
179179

180180
// If this is the final route segment, insert the value.
181-
if rest == remaining.len() {
181+
if terminator == remaining.len() {
182182
node.value = Some(UnsafeCell::new(val));
183183
node.remapping = remapping;
184184
return Ok(());
185185
}
186186

187187
// Otherwise, the previous node will hold only the suffix and we
188188
// need to create a new child for the remaining route.
189-
remaining = remaining.slice_off(rest);
189+
remaining = remaining.slice_off(terminator);
190190

191191
// Create a static node unless we are inserting a parameter.
192192
if remaining[0] != b'{' || remaining.is_escaped(0) {
@@ -468,16 +468,30 @@ impl<T> Node<T> {
468468
let next = rest[0];
469469
remaining = rest;
470470

471-
// If there is a single child node, we can continue searching in the child.
472-
if node.children.len() == 1 {
473-
// The route matches, remove the node.
474-
if node.children[0].prefix.unescaped() == remaining {
475-
return node.remove_child(0, &remapping);
476-
}
471+
// If this is a parameter node, we have to find the matching suffix.
472+
if matches!(node.node_type, NodeType::Param { .. }) {
473+
let terminator = remaining
474+
.iter()
475+
.position(|&b| b == b'/')
476+
.map(|b| b + 1)
477+
.unwrap_or(remaining.len());
477478

478-
// Otherwise, continue searching.
479-
node = &mut node.children[0];
480-
continue 'walk;
479+
let suffix = &remaining[..terminator];
480+
481+
for (i, child) in node.children.iter().enumerate() {
482+
// Find the matching suffix.
483+
if *child.prefix == *suffix {
484+
// If this is the end of the path, remove the suffix node.
485+
if terminator == remaining.len() {
486+
return node.remove_child(i, &remapping);
487+
}
488+
489+
// Otherwise, continue searching.
490+
remaining = &remaining[terminator - child.prefix.len()..];
491+
node = &mut node.children[i];
492+
continue 'walk;
493+
}
494+
}
481495
}
482496

483497
// Find a child node that matches the next character in the route.
@@ -493,7 +507,7 @@ impl<T> Node<T> {
493507
}
494508

495509
// If there is no matching wildcard child, there is no matching route.
496-
if !node.wild_child || remaining.first().zip(remaining.get(2)) != Some((&b'{', &b'}')) {
510+
if !node.wild_child {
497511
return None;
498512
}
499513

@@ -518,25 +532,21 @@ impl<T> Node<T> {
518532

519533
// If the node does not have any children, we can remove it completely.
520534
let value = if self.children[i].children.is_empty() {
521-
// Removing a single child with no indices.
522-
if self.children.len() == 1 && self.indices.is_empty() {
523-
self.wild_child = false;
524-
self.children.remove(0).value.take()
525-
} else {
526-
// Remove the child node.
527-
let child = self.children.remove(i);
528-
529-
match child.node_type {
530-
// Remove the index if we removed a static prefix.
531-
NodeType::Static => {
532-
self.indices.remove(i);
533-
}
534-
// Otherwise, we removed a wildcard.
535-
_ => self.wild_child = false,
535+
// Remove the child node.
536+
let child = self.children.remove(i);
537+
538+
match child.node_type {
539+
// Remove the index if we removed a static prefix that is
540+
// not a suffix node.
541+
NodeType::Static if !matches!(self.node_type, NodeType::Param { .. }) => {
542+
self.indices.remove(i);
536543
}
537544

538-
child.value
545+
// Otherwise, we removed a wildcard.
546+
_ => self.wild_child = false,
539547
}
548+
549+
child.value
540550
}
541551
// Otherwise, remove the value but preserve the node.
542552
else {
@@ -657,7 +667,6 @@ impl<T> Node<T> {
657667
};
658668

659669
// Store the parameter value.
660-
// Parameters are normalized so the key is irrelevant for now.
661670
params.push(b"", path);
662671

663672
// Remap the keys of any route parameters we accumulated during the
@@ -677,7 +686,7 @@ impl<T> Node<T> {
677686
};
678687

679688
// Store the parameter value.
680-
// Parameters are normalized so the key is irrelevant for now.
689+
// Parameters are normalized so this key is irrelevant for now.
681690
params.push(b"", param);
682691

683692
// Continue searching.

tests/remove.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ fn catchall() {
134134
RemoveTest {
135135
routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"],
136136
ops: vec![
137+
(Remove, "/foo/{catchall}", None),
137138
(Remove, "/foo/{*catchall}", Some("/foo/{*catchall}")),
138139
(Remove, "/bar/", Some("/bar/")),
139140
(Insert, "/foo/*catchall", Some("/foo/*catchall")),
@@ -263,3 +264,38 @@ fn check_escaped_params() {
263264
}
264265
.run();
265266
}
267+
268+
#[test]
269+
fn wildcard_suffix() {
270+
RemoveTest {
271+
routes: vec![
272+
"/foo/{id}",
273+
"/foo/{id}/bar",
274+
"/foo/{id}bar",
275+
"/foo/{id}bar/baz",
276+
"/foo/{id}bar/baz/bax",
277+
"/bar/x{id}y",
278+
"/bar/x{id}y/",
279+
"/baz/x{id}y",
280+
"/baz/x{id}y/",
281+
],
282+
ops: vec![
283+
(Remove, "/foo/{id}", Some("/foo/{id}")),
284+
(Remove, "/foo/{id}bar", Some("/foo/{id}bar")),
285+
(Remove, "/foo/{id}bar/baz", Some("/foo/{id}bar/baz")),
286+
(Insert, "/foo/{id}bax", Some("/foo/{id}bax")),
287+
(Insert, "/foo/{id}bax/baz", Some("/foo/{id}bax/baz")),
288+
(Remove, "/foo/{id}bax/baz", Some("/foo/{id}bax/baz")),
289+
(Remove, "/bar/x{id}y", Some("/bar/x{id}y")),
290+
(Remove, "/baz/x{id}y/", Some("/baz/x{id}y/")),
291+
],
292+
remaining: vec![
293+
"/foo/{id}/bar",
294+
"/foo/{id}bar/baz/bax",
295+
"/foo/{id}bax",
296+
"/bar/x{id}y/",
297+
"/baz/x{id}y",
298+
],
299+
}
300+
.run();
301+
}

0 commit comments

Comments
 (0)