Skip to content

Commit

Permalink
Implement regex matching for request path segments
Browse files Browse the repository at this point in the history
Adds anchored (^..$) regex matching to the request path segment handling
and drops the `unimplemented!()` macro in favor of a concrete
implementation.

This also adds an `OrderedRegex` newtype as the exist Regex didn't
implement the traits required for PartialEq, Eq, PartialOrd, and Ord.
See the following Github issues for reasons why Regex doesn't natively
implement this:
rust-lang/regex#313
rust-lang/regex#364

This (hopefully) addresses and closes #10.
  • Loading branch information
ELD committed Sep 9, 2017
1 parent 139f9bc commit b06e64d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target
Cargo.lock
.idea/

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rand = "0.3"
linked-hash-map = "0.4"
num_cpus = "1"
crossbeam = "0.3"
regex = "0.2"

[dev-dependencies]
gotham_derive = { version = "0.1.0", path = "gotham_derive" }
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extern crate base64;
extern crate rmp_serde;
extern crate linked_hash_map;
extern crate num_cpus;
extern crate regex;
#[cfg(windows)]
extern crate crossbeam;

Expand Down
65 changes: 62 additions & 3 deletions src/router/tree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,49 @@ use std::cmp::Ordering;
use std::collections::HashMap;
use std::borrow::Borrow;
use hyper::StatusCode;
use regex::Regex;

use http::PercentDecoded;
use router::route::{Route, Delegation};
use router::tree::{SegmentsProcessed, SegmentMapping, Path};
use state::{State, request_id};

pub struct OrderedRegex(Regex);

impl<'a> OrderedRegex {
pub fn new(regex: &'a str) -> Self {
OrderedRegex(Regex::new(&format!("^{pattern}$", pattern = regex)).unwrap())
}
}

impl PartialEq for OrderedRegex {
fn eq(&self, other: &Self) -> bool {
self.0.as_str() == other.0.as_str()
}
}

impl Eq for OrderedRegex {}

impl PartialOrd for OrderedRegex {
fn partial_cmp(&self, other: &OrderedRegex) -> Option<Ordering> {
return if self.0.as_str() > other.0.as_str() {
Some(Ordering::Greater)
} else if self.0.as_str() < other.0.as_str() {
Some(Ordering::Less)
} else if self.0.as_str() == other.0.as_str() {
Some(Ordering::Equal)
} else {
None
}
}
}

impl Ord for OrderedRegex {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}

/// Indicates the type of segment which is being represented by this Node.
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum SegmentType {
Expand All @@ -22,7 +59,7 @@ pub enum SegmentType {
/// Uses the supplied regex to determine match against incoming request paths.
Constrained {
/// Regex used to match against a single segment of a request path.
regex: String,
regex: OrderedRegex,
},

/// Matches any corresponding segment for incoming request paths.
Expand Down Expand Up @@ -263,8 +300,9 @@ impl Node {
fn is_match(&self, req_path_segment: &PercentDecoded) -> bool {
match self.segment_type {
SegmentType::Static => self.segment == req_path_segment.val(),
// TODO #10, address Constrained type
SegmentType::Constrained { regex: _ } => unimplemented!(),
SegmentType::Constrained { ref regex } => {
regex.0.is_match(req_path_segment.val().as_ref())
},
SegmentType::Dynamic | SegmentType::Glob => true,
}
}
Expand Down Expand Up @@ -526,6 +564,17 @@ mod tests {
seg3.add_child(seg4);
root.add_child(seg3);

// Ensure regex matching works and that it's anchored to the segment and does not allow for
// overzealous matching
// GET: /resource/<id> where id: [0-9]+
let mut seg_resource = NodeBuilder::new("resource", SegmentType::Static);
let mut seg_id = NodeBuilder::new("id", SegmentType::Constrained {
regex: OrderedRegex::new("[0-9]+")
});
seg_id.add_route(get_route(pipeline_set.clone()));
seg_resource.add_child(seg_id);
root.add_child(seg_resource);

// Ensure traversal will backtrack and find the correct path if it goes down an ultimately
// invalid branch, in this case seg6 initially being matched by the dynamic handler segdyn1
// which matches every segment it sees.
Expand Down Expand Up @@ -616,6 +665,16 @@ mod tests {
}
None => panic!("traversal should have succeeded here"),
}

let rs = RequestPathSegments::new("/resource/5001");
let expected_segment = "id";
match root.traverse(&rs.segments()) {
Some((path, _, sp, _)) => {
assert_eq!(path.last().unwrap().segment(), expected_segment);
assert_eq!(sp, 2);
},
None => panic!("traversal should have succeeded here"),
}
}

#[test]
Expand Down

0 comments on commit b06e64d

Please sign in to comment.